Jump to content
Moopler
Razz

Snippet Obtain Login Token [C#]

Recommended Posts

Hi boys 'n grills,

A while ago I re-reversed the way the Nexon Launcher obtains the login token, so I could use my CLB again as well as my launcher. In case you wanted to code your own CLB or launcher, you can use these snippets to authenticate your accounts and obtain the required login token.

Code

Spoiler

WebApi.cs


    /// <summary>
    /// Class that helps with accessing Nexon's WebAPI
    /// </summary>
    public class WebApi
    {
        private static Uri LoginUri => new Uri("https://accounts.nexon.net/account/login/launcher");

        private static Uri PassportUri => new Uri("https://api.nexon.io/users/me/passport");

        private static Uri BaseUri => new Uri("https://api.nexon.io");

        /// <summary>
        /// Generic method to access an API resource
        /// </summary>
        /// <typeparam name="TResult">The anonymous object to be deserialized from the received json</typeparam>
        /// <param name="uri">The API uri to access</param>
        /// <param name="accessToken">The access token to identify with</param>
        /// <param name="t">The anonymous object to be deserialized from the received json</param>
        /// <returns>A deserialized object, matching the structure as passed to <paramref name="t"/></returns>
        private static async Task<TResult> AccessApiResource<TResult>(Uri uri, string accessToken, TResult t)
        {
            var filter = new HttpClientHandler();
            using (var c = new HttpClient(filter))
            {
                c.DefaultRequestHeaders.UserAgent.ParseAdd("NexonLauncher.nxl-17.08.04-264-69a3683");
                c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken)));
                filter.CookieContainer.Add(BaseUri, new Cookie("nxtk", accessToken));

                string s = await c.GetStringAsync(uri).ConfigureAwait(false);

                return JsonConvert.DeserializeAnonymousType(s, t);
            }
        }

        /// <summary>
        /// Another token whose use is unknown
        /// </summary>
        public NexonToken IdentityToken { get; set; }

        /// <summary>
        /// Token used to authorize API usage
        /// </summary>
        public NexonToken AccessToken { get; set; }

        /// <summary>
        /// Authenticates the user and obtains an access token to be used with Nexon's API
        /// </summary>
        public async Task GetAccessToken(string email, string password)
        {
            byte[] hash;

            using (SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider())
            {
                byte[] byteText = Encoding.UTF8.GetBytes(password);
                hash = sha.ComputeHash(byteText);
            }

            using (HttpClient c = new HttpClient())
            {
                c.DefaultRequestHeaders.UserAgent.ParseAdd("NexonLauncher node-webkit/0.14.6 (Windows NT 10.0; WOW64) WebKit/537.36 (@c26c0312e940221c424c2730ef72be2c69ac1b67) nexon_client");

                string json = await Task.Factory.StartNew(() =>
                {
                    return JsonConvert.SerializeObject(new
                    {
                        id = email,
                        password = string.Concat(hash.Select(b => b.ToString("X2"))).ToLower(),
                        client_id = "7853644408",
                        scope = "us.launcher.all",
                        device_id = "b954edb18e12e65c1c59e23cb1b85ad48db0a334f861395c6aa0b80f2bf05713"
                    });
                });

                StringContent content = new StringContent(json, Encoding.UTF8, "application/json");

                HttpResponseMessage response = await c.PostAsync(LoginUri, content).ConfigureAwait(false);
                string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    var result = JsonConvert.DeserializeAnonymousType(responseContent, new
                    {
                        id_token = "",
                        access_token = "",
                        user_no = 0,
                        id_token_expires_in = default(uint),
                        access_token_expires_in = default(uint),
                        is_verified = default(bool)
                    });

                    IdentityToken = new NexonToken(result.id_token, result.id_token_expires_in);
                    AccessToken = new NexonToken(result.access_token, result.access_token_expires_in);
                }
                else
                {
                    var error = JsonConvert.DeserializeAnonymousType(responseContent, new
                    {
                        code = "",
                        message = "",
                        description = ""
                    });

                    //TODO: Log error
                    throw new NotImplementedException();
                }
            }
        }

        /// <summary>
        /// Gets the passport token needed to login ingame
        /// </summary>
        /// <param name="token">The access token to be used for the API</param>
        /// <returns>The Nexon Passport</returns>
        public async Task<NexonPassport> GetPassport(string token)
        {
            if (AccessToken == null)
                return new NexonPassport(ResultStatus.InvalidToken);

            var result = await AccessApiResource(PassportUri, AccessToken.Token, new
            {
                user_no = 0,
                membership_no = 0,
                passport = "",
                auth_token = ""
            });

            return new NexonPassport(result.passport);
        }
    }

NexonPassport.cs


    public class NexonPassport
    {
        public string Token { get; set; }

        public ResultStatus Status { get; set; }

        public NexonPassport(string token)
        {
            Token = token;
            Status = ResultStatus.OK;
        }

        public NexonPassport(ResultStatus status)
        {
            Status = status;
        }
    }

NexonToken.cs


    public class NexonToken
    {
        public string Token { get; set; }

        public DateTime ExpiresAt { get; set; }

        public NexonToken(string token, uint secondsTilExp)
        {
            Token = token;
            ExpiresAt = DateTime.Now.AddSeconds(secondsTilExp);
        }   
    }
    
    public static class WebApiExtensions
    {
        public static bool IsValid(this NexonToken t)
        {
            if (t == null)
                return false;

            if (DateTime.Now > t.ExpiresAt)
                return false;
            else
                return true;
        }
    }

ResultStatus.cs


    public enum ResultStatus
    {
        OK,
        BadRequest,
        InvalidToken,
        Unknown
    }

How to use

WebApi webApi = new WebApi();
            
if(!webApi.AccessToken.IsValid())
{
    await webApi.GetAccessToken(s.Context.Profile.Email, s.Context.Profile.Password);
}

NexonPassport passport = await webApi.GetPassport(webApi.AccessToken.Token);

Console.WriteLine(passport.Token);

Credits to Waty for the original structure of the WebApi class.

  • Like 3
  • Thanks 2

Share this post


Link to post
Guest

Thanks @Razz I think I still have some of my notes lying around. You should also put in a heartbeat function; like from the original launcher; that refreshes the login tokens every 270 seconds (could be off; remembering off top of head). Probably a single background thread  would work well on call to the login function you have. Only problem would probably checking if a current session exists. ~~Just some thoughts. Looks great though.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×