JWT authentication with role Claims in ASP.NET Core Identity

Asked

Viewed 941 times

5

SOLVED - Authorize with roles

With the help of this link: https://stackoverflow.com/a/47025009/10647645

And a reply in the post with the final files with the resolution

UNSOLVED - Identityserver, how to do and use?

I have 3 initial problems here:

  1. Token decoding: when receiving the request, I need to decode the token to validate and allow access to the roles with Authorize.

  2. Authorize with roles in my system: putting a policy I managed to do, but with the roles I could not.

  3. Separate solutions: I have the solution of Identity and the system itself, therefore, I need to know how to make the "system itself visualize" the Identity to validate the token and so, making the authorization of the token.


ARCHIVES

appsettings.json

"JwtKey": "MYSECRET",
"JwtIssuer": "http://localhost:8040/",
"JwtExpireDays": 30,

Startup.Cs

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var key = Encoding.ASCII.GetBytes(Configuration["JwtKey"]);
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
    };
});

// Usando a politica
services.AddAuthorization(auth =>
{
    auth.AddPolicy("BearerPolicy", new AuthorizationPolicyBuilder()           
      .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
      .RequireAuthenticatedUser().Build());
});

Accountcontroller.Cs

[HttpPost]
[AllowAnonymous]
public async Task<AccountModel> Login([FromBody] AccountModel model)
{
    var usuario = new ApplicationUser();
    ...
    var appUser = _userManager.Users.Include(...);

    var claims = await GetValidClaims(appUser);
    var accountModel = new AccountModel(usuario, _roleManager);
    accountModel.Token = GenerateJwtToken(appUser, claims);

    return accountModel;
}

private string GenerateJwtToken(ApplicationUser user, List<Claim> claims)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["JwtKey"]);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),

        Expires = DateTime.UtcNow.AddDays(1),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}
private async Task<List<Claim>> GetValidClaims(ApplicationUser user)
{
    IdentityOptions _options = new IdentityOptions();
    var claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
        new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName)
    };
    var userClaims = await _userManager.GetClaimsAsync(user);
    var userRoles = await _userManager.GetRolesAsync(user);
    claims.AddRange(userClaims);
    foreach (var userRole in userRoles)
    {
        claims.Add(new Claim(ClaimTypes.Role, userRole));
        var role = await _roleManager.FindByNameAsync(userRole);
        if (role != null)
        {
            var roleClaims = await _roleManager.GetClaimsAsync(role);
            foreach (Claim roleClaim in roleClaims)
            {
                claims.Add(roleClaim);
            }
        }
    }
    return claims;
}

Rolemodel

public class RoleModel
{
    public string Id{ get; set; }
    public string Nome { get; set; }
    public string NomeNormalizado { get; set; }
    public string SeloConcorrencia { get; set; }

    public RoleModel() { }

    public RoleModel(ApplicationRole applicationRole)
    {
        Id = applicationRole.Id;
        Nome = applicationRole.Name;
        NomeNormalizado = applicationRole.NormalizedName;
        SeloConcorrencia = applicationRole.ConcurrencyStamp;
    }
}

Applicationuser

public class ApplicationUser : IdentityUser
{
    public string PrimeiroNome { get; set; }
    public string UltimoNome { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; } = new List<ApplicationUserRole>();
}

Applicationrole

public class ApplicationRole: IdentityRole
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; } = new List<ApplicationUserRole>();
}

Applicationuserrole

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}

Request POST Angular

AdicionarPessoa(pessoa: Pessoa): Observable<Pessoa> {
    return this.httpClient.post<Pessoa>(environment.url + "/Pessoa/AdicionarPessoa", pessoa, environment.token);
  };

Environment

token: { headers: {'Authorization':'Bearer ' + localStorage.getItem("token")}}

Personal controller

[HttpPost]
[Route("api/Pessoa/AdicionarPessoa")]
// [Authorize("BearerPolicy")] <-- Usando a politica
[Authorize(Roles = "Administrador")]
public Pessoa AdicionarPessoa([FromBody]Pessoa model)

ERROR: http://localhost/api/Person/Add 401 (Unauthorized)

EDITIONS

I did the example in another method, so I don’t need to pass a json in the post:

My Token

inserir a descrição da imagem aqui

FIRST ATTEMPT - FAILURE

Get all users with Role Administrator

[HttpGet]
[Route("api/Pessoa/ObterTodosPessoas")]
[Authorize(Roles = "Administrador")]
public IEnumerable<Pessoa> ObterTodosPessoas()
{
    var PessoaRepositoryVar = new PessoaRepository();
    var Pessoas = PessoaRepositoryVar.ObterTodos();
    return Pessoas;
}

inserir a descrição da imagem aqui

Console POSTMAN

inserir a descrição da imagem aqui


I made some modifications since the day I posted here, so I’m editing the post to stay as equal as possible.

During some time of research and study, I came across some doubts:

At the beginning of the post, I said that I had 3 initial problems, I will answer them and I would like you to say "that’s it, this right so..." or "no, this wrong, think this way:...." I thank you from the beginning!

  1. Decode the token

    There is no reason why I decode the token, since Authorize and Authentication is done in startup.Cs

  2. Authorize

    I managed to do, in the reply of the post has as I did

  3. Separate solutions

    I don’t know if it’s clear to everyone, but roughly speaking, I need to open 2 visual studio, 1 to load Identity and another to load the other solution. And about Identityserver, I searched little about, I want to be able to make work the roles in Identity and then try to make Identityserver and put in the rest of the system... If anyone has any material that explains this, I’d appreciate it


ARCHIVES 07/08/2019

Startup.Cs

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseOracle(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddIdentity<ApplicationUser, ApplicationRole>()
          .AddEntityFrameworkStores<ApplicationDbContext>()
          .AddDefaultUI()
          .AddDefaultTokenProviders();

    services.AddCors();

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    var key = Encoding.ASCII.GetBytes(Configuration["JwtKey"]);
    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
        };
    });
}

Accountcontroller.Cs

[HttpPost]
[Route("Login")]
public async Task<AccountModel> Login([FromBody] AccountModel model)
{

    if (ModelState.IsValid)
    {
        var usuario = new ApplicationUser();
        var signInResultado = new Microsoft.AspNetCore.Identity.SignInResult();


        Task.Run(async () =>
        {
             usuario = await _userManager.FindByEmailAsync(model.Email);
        }).Wait();

            ... 

        if (signInResultado.Succeeded)
        {
            var appUser = _userManager.Users.FirstOrDefault(u => u.Id == usuario.Id);

            var claims = await GetValidClaims(appUser);
            var accountModel = new AccountModel(usuario, _roleManager);

            accountModel.Token = GenerateJwtToken(appUser, claims);
            return accountModel;
        }

    }
    return model;
}
private string GenerateJwtToken(ApplicationUser user, List<Claim> claims)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["JwtKey"]);

    // AQUI TENHO 2 FORMAS DE FAZER, NÃO SEI QUAL FUNCIONA OU QUAL A MAIS CORRETA
    // FORMA 1
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),

        Expires = DateTime.UtcNow.AddDays(1),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);

    // FORMA 2
    var tokens = new JwtSecurityToken(
        claims: claims,
        expires: DateTime.UtcNow.AddDays(1),
        signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        );

    return new JwtSecurityTokenHandler().WriteToken(tokens);

}
private async Task<List<Claim>> GetValidClaims(ApplicationUser user)
{
    IdentityOptions _options = new IdentityOptions();
    var claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
        new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName)
    };
    var userClaims = await _userManager.GetClaimsAsync(user);
    var userRoles = await _userManager.GetRolesAsync(user);
    claims.AddRange(userClaims);
    foreach (var userRole in userRoles)
    {
        claims.Add(new Claim(ClaimTypes.Role, userRole));
        var role = await _roleManager.FindByNameAsync(userRole);
        if (role != null)
        {
            var roleClaims = await _roleManager.GetClaimsAsync(role);
            foreach (Claim roleClaim in roleClaims)
            {
                claims.Add(roleClaim);
            }
        }
    }
    return claims;
}

The other files remain the same...

My biggest difficulty at the moment is getting C# to identify/validate/authorize the roles you have in the token passed by the header!

  • Friend, also put your request in the POST so we can see how you are doing it, it can be something in it.

  • @Mayconf.Castro, I don’t know if I put what I said

  • Friend you have to pass your token in the header of your request, from what I see, you are just firing the url so it is returning 401 - unauthorized. Try to add your token to the header of your request and tell me if it worked.

  • Can you help me with that? I don’t know how to put information in the request header

  • 1

    Amigo check out this link: https://stackoverflow.com/questions/45286764/angular-httpclient-doesnt-send-header

  • In your case the header should be, Authorization and your access token

  • I re-wanted by Postman, edited the post, gave 401 Unauthorized

  • I added a policy and I did with the Authorize("Policy"), worked within Identity, but the other solutions that are separate, do not work. But not only that, I would like to do by the roles too: example: Authorize(Roles = "Adm")

Show 3 more comments

1 answer

0


I will post here, as the files with the resolution for Authorize with the roles. Important: I have not done with Identityserver yet.

Startup.Cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>

        options.UseOracle(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, ApplicationRole>()
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultUI()
              .AddDefaultTokenProviders();

        services.AddCors();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        var symetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"]));
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["JwtIssuer"],
                    ValidAudience = Configuration["JwtIssuer"],
                    IssuerSigningKey = symetricSecurityKey

                };
            });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseCors(c =>
        {
            c.AllowAnyOrigin();
            c.AllowAnyMethod();
            c.AllowAnyHeader();
        });

        app.Use(async (context, next) =>
        {
            var authHeader = context.Request.Headers["Authorization"].ToString();
            if (authHeader != null && authHeader.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
            {
                var tokenStr = authHeader.Substring("Bearer ".Length).Trim();

                var handler = new JwtSecurityTokenHandler();
                var token = handler.ReadToken(tokenStr) as JwtSecurityToken;
                var nameid = token.Claims.First(claim => claim.Type.Contains("nameidentifier")).Value;

                var identity = new ClaimsIdentity(token.Claims);
                context.User = new ClaimsPrincipal(identity);
            }
            await next();
        });
        app.UseAuthentication();

        app.UseMvc(routes =>
        {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

Accountcontroller.Cs

[HttpPost]
[Route("login")]
public async Task<AccountModel> Login([FromBody] AccountModel model)
{
    if (ModelState.IsValid)
    {
        var usuario = await _userManager.FindByEmailAsync(model.Email);
        await _userManager.CheckPasswordAsync(usuario, model.Senha);

        var signInResultado = await _signInManager
            .PasswordSignInAsync(
                usuario.UserName,
                model.Senha,
                isPersistent: false,
                lockoutOnFailure: false
            );

        if (signInResultado.Succeeded)
        {
            var appUser = _userManager.Users.FirstOrDefault(u => u.Id == usuario.Id);
            var claims = await GetValidClaims(appUser);

            var accountModel = new AccountModel(usuario, _roleManager);
            accountModel.Token = GenerateJwtToken(appUser, claims);
            return accountModel;
        }
    }
    return model;
}

private string GenerateJwtToken(ApplicationUser user, List<Claim> claims)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_configuration["JwtKey"]);

    var tokens = new JwtSecurityToken(
        issuer: _configuration["JwtIssuer"],
        audience: _configuration["JwtIssuer"],
        claims: claims,
        expires: DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"])),
        signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        );

    return new JwtSecurityTokenHandler().WriteToken(tokens);

}

private async Task<List<Claim>> GetValidClaims(ApplicationUser user)
{
    IdentityOptions _options = new IdentityOptions();
    var claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(_options.ClaimsIdentity.UserIdClaimType, user.Id.ToString()),
        new Claim(_options.ClaimsIdentity.UserNameClaimType, user.UserName)
    };
    var userClaims = await _userManager.GetClaimsAsync(user);
    var userRoles = await _userManager.GetRolesAsync(user);
    claims.AddRange(userClaims);
    foreach (var userRole in userRoles)
    {
        claims.Add(new Claim(ClaimTypes.Role, userRole));
        var role = await _roleManager.FindByNameAsync(userRole);
        if (role != null)
        {
            var roleClaims = await _roleManager.GetClaimsAsync(role);
            foreach (Claim roleClaim in roleClaims)
            {
                claims.Add(roleClaim);
            }
        }
    }
    return claims;
}

Authorize

[HttpGet]
[Authorize(Roles = "Administrador")]
public ActionResult<IEnumerable<RoleModel>> Get()

Request Header Angular

getAll(): Observable<...[]> {
    return this.httpClient.get<...[]>(environment.url + "api",
    { headers: {'Authorization' : 'Bearer ' + token });
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.