14

I am currently working on an interaction between Angular JS app and Node.js Server (as API) with an authentication based on JSON Web Token.

But I have a question I can't answer by myself : when you encode the JWT server-side putting a user as payload, how do you proceed to retrieve the user information client-side ? Here is a small example to understand my question:

I am a basic user, I send my credentials to the API for authenticating. In exchange, I receive a JWT token but I don't have any information about the user since only the server has the secret key that is able to decode the JWT token. So does the server need to send me for example the id of the user so that I could call my api user/id for retrieving information about the user authenticated?

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
ChrisV
  • 233
  • 4
  • 15

4 Answers4

10

You retrieve the user's info by decoding the token on each request. So in your example after the token is returned to the client, the client makes a request to the server to grab the user's first and last name using the data stored in the encoded token which is sent along with the request back to the server. When making this GET request, you can send the token as a parameter. I'll use a non-cookie stored example. Here's how it goes down:

  1. The user signs in with their password and username
  2. The server encodes a json web token payload that contains the unique identifier (i.e. user_id) of the user that signed in using the secret_key. An example function call may look something like this.

payload = {user_id: 35} user_token = JWT.encode(payload, "your_secret_key");

  1. Return the user_token to the client and store said token in a hidden html tag or in a localStorage variable. Using Angular, I'd store it in localStorage.

  2. Now that the user is signed_in and the token is client-side, you can submit a GET request that contains the user_token as a parameter. Remember, this user_token payload contains the user_id.

  3. The server gets the parameter and decodes the user_token to get the user_id from the payload.

  4. You query the database with the user_id and return the data (first and last name) as plain json, NOT ENCODED.

It's important to remember the only thing to encode in your example is the unique identifier (user_id). On each request you decode the token which itself is the authentication mechanism.

Micah Frazier
  • 141
  • 1
  • 4
  • Thanks for your answer but if you do so how can you use information client side. I will explain again by an example if you are "John Doe" user and you have username jdoe and password pwd, you submit jdoe and pwd and receive a token. But how can you retrieve the information of the user that is stocked in the database like for example first and last name in order to display it in the app ? – ChrisV Sep 01 '14 at 03:40
  • 1
    Thanks Micah for editinng, it is clearer. I thought to it after and your answer confirms this. Thanks a lot. Good evening. – ChrisV Sep 05 '14 at 00:05
  • Is it true that the 'sub' (subject) key of the payload is defined by the JWT spec to be used to identify the subject of this token's claims, i.e. commonly enough this will be the place to put the user ID, rather than an arbitrary 'user_id' key. Is this so, or am I misinterpreting? (https://tools.ietf.org/html/rfc7519#section-4.1.2) – Jonathan Hartley Feb 22 '17 at 20:57
9

You have the payload on the client, If your needed data is in the payload you can easily do a Base64 Decode on payload to find it!

To understand this here are steps:

  1. Client send username:user,password:pass to server.

  2. The server starts the authentication business and finds that the user name and password is valid.

  3. The server must return these information back to client. Here is where JWT has some rules. The server must return a token back to client. The token has three parts Header.PayLoad.Signature . Forget about signature right now, which is the part which make some confusion.

The part one is Header. Some thing like:

{"typ":"JWT","alg":"HS256"}

Which will be eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 after Base64 Decode. Please consider this is just a decode, no encryption at all! To see this you can go to https://www.base64decode.org/ and test.

After the header, the server needs to send a payload to user. The server may decide to send below json ( I said decide, because there is no standard requirement here, you can send more or less data as payload, for example, you may also set user privileges for example admin:true, or user first and last name, but keep in mind that the JWT size must be small as it will be send to server on each request)

{"username":"user","id":3,"iat":1465032622,"exp":1465050622}

Again according to JWT, the server needs a Base64 Decode here ( and again no encryption at all). The above json will be eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.

Until now the server created the Header and Payload. Now time to make signature! It is very easy:

var encodedString=base64UrlEncode(header) + "." + base64UrlEncode(payload);
//As our example base64UrlEncode(header) is eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
//and the base64UrlEncode(payload) is eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9

 var signature=HMACSHA256(encodedString, 'a secret string which is kept at server');

The signature is made with a secret key which you don't have it at clent!! You don't need it either. All token data is in the payload and can be accessed with decode ( again no decrypt ! ).

This signature is used at the server, when you send token back to server, the server check that signiature is correct to make sure he can trust the token data.

To summarize have a look at below token

//Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
//PayLoad
eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
//Signature
0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg

Header and payloads are Base64 Decoded and you can encode it on client. But you can not do any thing with signature.

The signature is only used by the server. The client send each request with his token, the server must be sure that the client did not change any part of token payload (for example change userid). This is where the signature string come importance is revealed, the server recheck the signature with it's secret key for every request!

Note:

Do you still wonder why the JWT use encode and decode ?! To make the hole token URL safe !

Alireza Fattahi
  • 42,517
  • 14
  • 123
  • 173
6

The strategy in the accepted answer works, but it misses the fact that the client can see the payload of a JWT. It is explained nicely in The Anatomy of a JSON Web Token.

A JWT has 3 parts. The first two, header and payload, are base64 encoded. The client can decode them easily. The payload has claims about the user, the client can use this data (user id, name, roles, token expiration) w/out having to make another request to the server.

The third part of the JWT is the signature. It is a hash of the header, the payload, and a secret that only the server knows. The server will validate the token and user's permissions on every request.

The client never knows the secret, it just has a token that claims to be the given user.

Sunil D.
  • 17,983
  • 6
  • 53
  • 65
  • How does the client never know the secret? It needs the secret to accurately compose the token... – nobody Nov 10 '15 at 19:17
  • Or is your expectation that the server would be the only source assigning tokens payloads and headers together? That you shouldn't have clients encoding their own payloads? – nobody Nov 10 '15 at 19:25
  • 1
    @BenNelson exactly, the server is the only thing that should be generating tokens. If you let the client generate tokens, you open yourself up to attacks from malicious users b/c they would be able to generate a token with any kind of payload. – Sunil D. Nov 10 '15 at 19:48
  • 1
    If I capture someone's token then I can impersonate. What mechanism can be used to prevent this? – variable Mar 30 '20 at 08:25
  • 1
    @variable I don't think there is one, the same thing can happen if you can capture someone's cookies. You should always use HTTPS and set an appropriate expiration date on the JWT. But if someone can get access to the user's browser they can do whatever they want w/the cookie/JWT. – Sunil D. Apr 05 '20 at 21:03
-1

JWT (JSON web token) has become more and more popular in web development. It is an open standard which allows transmitting data between parties as a JSON object in a secure and compact way. The data transmitting using JWT between parties are digitally signed so that it can be easily verified and trusted.

JWT in ASP.NET Core

The first step is to configure JWT based authentication in our project. we can add custom jwt auth middleware that fire in every request for Authorization.

Startup.cs

    services.AddMvc(options => options.EnableEndpointRouting = false);
    var tokenValidationParams = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey("Jwt_Key"),
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                RequireExpirationTime = false,
                ValidIssuer = "Jwt_Issuer",
                ValidAudience = "Jwt_Audience",
                ClockSkew = TimeSpan.Zero
            };

            services.AddSingleton(tokenValidationParams);

            services.AddAuthentication(options => {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(jwt => {
                jwt.SaveToken = true;
                jwt.TokenValidationParameters = tokenValidationParams;
            });

            services.AddMvc();
            
            
            
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
                    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
                    {
                        if (env.IsDevelopment())
                        {
                            app.UseDeveloperExceptionPage();
                        }
                        // custom jwt auth middleware
                        **app.UseMiddleware<JwtMiddleware>();**
                        app.UseAuthentication();
                        app.UseMvc();
                        app.Run(async (context) =>
                        {
                            await context.Response.WriteAsync("Welcome to DATA API");
                        });
            
                    }

        

Generete JWT

GenerateJSONWebToken(User userInfo)
                {
                    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Jwt_Key"));
                    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
                    var claims = new[] {
                        new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserID),
                        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                    };
        
                    var token = new JwtSecurityToken("Jwt_Issuer","Jwt:Audience",
                        claims,
                        expires: DateTime.Now.AddHours(24),
                        signingCredentials: credentials);
        
                    return new JwtSecurityTokenHandler().WriteToken(token);
                }
    

This Method return JWT Totken like

Token : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKa WduZXNoIFRyaXZlZGkiLCJlbWFpbCI6InRlc3QuYnRlc3RAZ21haWwuY29tIiwiRG F0ZU9mSm9pbmciOiIwMDAxLTAxLTAxIiwianRpIjoiYzJkNTZjNzQtZTc3Yy00ZmU xLTgyYzAtMzlhYjhmNzFmYzUzIiwiZXhwIjoxNTMyMzU2NjY5LCJpc3MiOiJUZXN0 LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.8hwQ3H9V8mdNYrFZSjbCpWSyR1CNyDYHc Gf6GqqCGnY"

Calling Authorize Method

 [Authorize]
   public ActionResult<IEnumerable<string>> Get()
   {
    return new string[] { "value1", "value2", "value3", "value4",
    "value5" };
   }
    

Validate Token in Jwt Middleware Class

JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly TokenValidationParameters _tokenValidationParams;
        public JwtMiddleware(RequestDelegate next, TokenValidationParameters 
        tokenValidationParams)
        {
            _next = next;
            _tokenValidationParams = tokenValidationParams;
        }

        public async Task Invoke(HttpContext context)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            var jwtTokenHandler = new JwtSecurityTokenHandler();
            // Validation 1 - Validation JWT token format
            var tokenInVerification = jwtTokenHandler.ValidateToken(token, _tokenValidationParams, out var validatedToken);

            if (validatedToken is JwtSecurityToken jwtSecurityToken)
            {
                var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase);

                if (result == false)
                {
                    Error Invalid = new Error()
                    {
                        Success = false,
                        Errors = "Token is Invalid"
                    };

                    context.Items["Error"] = Invalid;
                }
            }

            await _next(context);
        }
    }

Authorize Attribute

public void OnAuthorization(AuthorizationFilterContext context)
 {
    var Error= (UserModel)context.HttpContext.Items["Error"];
    if (AuthResult != null)
    {
        // not logged in
        context.Result = new JsonResult(new { message = "Unauthorized Access" }) { 
     StatusCode = StatusCodes.Status401Unauthorized };
    }
 }

I hope this will work for you.

  • Please don't post only code as an answer, but also provide an explanation of what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes – Ran Marciano Apr 07 '21 at 05:17