4

I have implemented JWT tokens to know who the current user is in an API application that is being used by an MVC controller. I'm building something like a forum app. The user must be logged in to be able to post, so I'm basically trying to use the JWT token to store the current user's email. When the user click on "Create Post" the action should get the token and its value, the problem is that I don't know how to use the token to protect controllers or retrieve data from current user, I have already copied and pasted the token in jwt.io to check if the data is stored correctly in the token and the value (the user's email) is stored correctly.

The API controller with the "login" action:

    public async Task<IActionResult> login([FromBody] Usuario model)
    {
                //check if user exists and the password is correct

                //generates the token
                    var SecretKey = config.GetValue<string>("SecretKey");
                    var key = Encoding.ASCII.GetBytes(SecretKey);

                    var claims = new ClaimsIdentity(new Claim[] 
                    {
                        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                        new Claim(ClaimTypes.Name, user.Mail)
                    });
                    claims.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Mail));

                    var tokenDesc = new SecurityTokenDescriptor
                    {
                        Subject = claims,
                        Expires = DateTime.UtcNow.AddMinutes(20),
                        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                    };

                    var tokenHandler = new JwtSecurityTokenHandler();
                    var createdToken = tokenHandler.CreateToken(tokenDesc);

                    string bearer_token = tokenHandler.WriteToken(createdToken);

                    using(var client = new HttpClient())
                    {
                        client.DefaultRequestHeaders.Add("Authorization", "Bearer" + bearer_token);
                    }

                    return Ok(bearer_token);
                }
    }

The MVC controller from where the API is used:

    public async Task<IActionResult> login(Usuario model)
    {
            HttpClient hc = new HttpClient();
            hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");

            var login = await hc.PostAsJsonAsync<Usuario>("login", model);

            //check the response

            var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
            identity.AddClaim(new Claim(ClaimTypes.Name, model.Email));

            var principal = new ClaimsPrincipal(identity);

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);

            HttpContext.Session.SetString("JWToken", login.ToString());

            hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", login.ToString());

            return RedirectToAction("IndexForumList", "ForumControllerMVC");
        }
    }

This is the API method to "Create Posts" and where the token should be used, here the userId is null:

    public async Task<IActionResult> createPost([FromForm]ForumModel model)
    {
        string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        
        //retrieves the current user email, validates and save the content to database
        
    }

And this is the MVC method to "Create Posts":

public async Task<IActionResult> createPost(ForumModel model)
    {
        HttpClient hc = new HttpClient();
        hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");

        //var userPost = hc.PostAsJsonAsync<ForumModel>("Usuarios/createPost", model);

        var userPost = await hc.PostAsync("createPost", formContent);

        if(userPost.IsSuccessStatusCode == true)
        {
            return RedirectToAction("IndexForumList", "ForoControllerMVC");
        }
    }

I have been suffering with this due to my lack of knowledge about JWT, any help is appreciated.

UPDATE

The startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("SecretKey"));

        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });
        services.AddSession(
            options =>
            {
                options.IdleTimeout = TimeSpan.FromMinutes(10);
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
            });
    }
5
  • When you call from the mvc you need add token as header. Then you need to add authorize attribute in the api controller or action.i believe you have set up everything in the configure service. Commented Apr 29, 2021 at 4:28
  • You've added ClaimTypes.NameIdentifier twice. UserManager will look for claims IdentityOptions.ClaimsIdentity.UserIdClaimType / .UserNameClaimType to fetch the DB User. You can change the claim type name in startup. Are your "API application" and "MVC controller" the same program or separate? Commented Apr 29, 2021 at 5:58
  • @Selik alright, I think the part of the services are correctly configurated, I have updated that part on the question. Now I think i have one more question, how I add the token as header? Commented Apr 29, 2021 at 19:59
  • @JeremyLakeman so, I have to left only one ClaimTypes.NameIdentifier with the value of the email, or something like that? And yeah, the API is on the same project than the MVC. Commented Apr 29, 2021 at 20:20
  • Inside your createpost MVC method you have to add the authorization header for the httpclient call and add authorize attribute to the api action/ controller. httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + "your_token"); Commented Apr 30, 2021 at 0:27

1 Answer 1

6

If I understand your question correctly, To protect your API you can decorate it with [Authorize] attribute. Eg -

    [Authorize]
    [HttpGet]
    public IActionResult GetAll()
    {
        var users = _userService.GetAll();
        return Ok(users);
    }

And to validate your tokens since you are using .netcore for your api, you ll have to create a middleware that will validate the token before your requests hit the API endpoint. You can follow this tutorial for more details on how to use JWT with ASP.NET core.

To get user Id in your case, you ll have to validate the token first and then extract the UserId. Try changing your code in createPost api to this -

public async Task<IActionResult> createPost([FromForm]ForumModel model)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var SecretKey = config.GetValue<string>("SecretKey");
    var key = Encoding.ASCII.GetBytes(SecretKey);
    var token = HttpContext.Request.Headers["Authorization"];       
    
    tokenHandler.ValidateToken(token, new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false,
        ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

        var jwtToken = (JwtSecurityToken)validatedToken;
        var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "NameIdentifier").Value);
}

Although this should be handled in the middleware and you can then attach the authenticated user to the current HttpContext.Items collection to make it accessible within the scope of the current request. All this is explained in the tutorial in detail. Hope that helps.!

Sign up to request clarification or add additional context in comments.

2 Comments

thanks for the help, I have been reading the article, now on the part of tokenHandler.ValidateToken(token, new TokenValidationParameters... what does the token parameter refer to or where is it created to be used there?
oh i see. I have missed to add the token parameter in the code. It is basically your jwt token that you create in your login api. When you call your api and attach the authorization header, you should be able to access it by - var token = HttpContext.Request.Headers["Authorization"]; I have updated the code as well.. sorry for missing that out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.