0

I am using .NET version 5.0.100-rc.1.20452.10 , ASP.NET Core Web API, Microsoft SQL Server 2019, JWT token. I have Startup.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using shadow.Data;
using shadow.Models;
using shadow.Services;
using System.IO;
using System.Text;

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

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddHttpContextAccessor();
            //services.AddCors();
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader());
            });
            services.AddSingleton<IUriService>(o =>
            {
                var accessor = o.GetRequiredService<IHttpContextAccessor>();
                var request = accessor.HttpContext.Request;
                var uri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());
                return new UriService(uri);
            });
            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
            {
                //options.Password.RequireDigit = true;
                //options.Password.RequireLowercase = true;
                options.Password.RequiredLength = 6;
            }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
            services
                .AddAuthentication(auth =>
                {
                    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })

                  .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidAudience = Configuration["AuthSettings:Audience"],
                        ValidIssuer = Configuration["AuthSettings:Issuer"],
                        RequireExpirationTime = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthSettings:Key"])),
                        ValidateIssuerSigningKey = true
                    };
                });
            services.AddAuthorization(options =>
            {
                options.FallbackPolicy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();

            });
            services.AddScoped<IUserService, UserService>();
            services.AddTransient<IMailService, SendGridMailService>();            
            services.AddControllers();
            services.AddRazorPages();
            services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false);
            services.AddMvc().AddJsonOptions(options =>
            {
                //options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
            //app.UseHttpsRedirection();
            app.UseRouting();
            // app.UseCors(options => options.WithOrigins("http://localhost:4200")
            //.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
            app.UseCors("CorsPolicy");
            app.UseAuthentication();
            app.UseAuthorization();
            // Server: https://shorten.news/static-file/content/10992.mp3
            // Local: http://localhost:5000/static-file/content/10992.mp3
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(
                    // Path.Combine(env.ContentRootPath, @"c:\audio\")),
                    // Path.Combine(@"c:\audio\")),
                    Path.Combine(@"D:\shadow_backend\Upload\files\")
                ),
                RequestPath = "/static-file",
                OnPrepareResponse = context =>
                {
                    context.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
                }
            });

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync("cound not find anything");
            });
        }
    }

}

File IUserService.cs

using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using shadow.Models;
using shadow.Shared;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace shadow.Services
{

    public interface IUserService
    {

        Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model);
        Task<UserManagerResponse> LoginUserAsync(LoginViewModel model);
        Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model);
        Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token);
        Task<UserManagerResponse> ForgetPasswordAsync(string email);
        Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model);
        Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model);
    }

    public class UserService : IUserService
    {
        private Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManger;
        private IConfiguration _configuration;
        private IMailService _mailService;

        public UserService(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager, IConfiguration configuration, IMailService mailService)
        {
            _userManger = userManager;
            _configuration = configuration;
            _mailService = mailService;
        }

        public async Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model)
        {
            if (model == null)
            {
                throw new NullReferenceException("Reigster Model is null");
            }
            if (model.Password != model.ConfirmPassword)
            {
                return new UserManagerResponse
                {
                    Message = "Confirm password doesn't match the password",
                    IsSuccess = false,
                };
            }
            var identityUser = new ApplicationUser
            {
                Email = model.Email,
                UserName = model.Email,
                About = model.About,
                SecondMobile = model.SecondMobile,
                Fullname = model.Fullname,
                AliasName = model.AliasName,
                Created = DateTime.Now,
                Modified = DateTime.Now
            };
            var result = await _userManger.CreateAsync(identityUser, model.Password);

            if (result.Succeeded)
            {
                var confirmEmailToken = await _userManger.GenerateEmailConfirmationTokenAsync(identityUser);
                var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);
                var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);
                string url = $"{_configuration["AppUrl"]}/api/auth/ConfirmEmail?userId={identityUser.Id}&token={validEmailToken}";
                await _mailService.SendEmailAsync(identityUser.Email, "Confirm your email", $"<h1>Welcome to Trustee app</h1>" +
                    $"<p>Please confirm your email by <a href='{url}'>clicking here</a></p>");
                return new UserManagerResponse
                {
                    Message = "User created successfully!",
                    IsSuccess = true,
                };
            }
            return new UserManagerResponse
            {
                Message = "User did not create",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            };
        }

        /// <summary>
        /// Đăng nhập.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<UserManagerResponse> LoginUserAsync(LoginViewModel model)
        {
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return new UserManagerResponse
                {
                    Message = "There is no user with that Email address",
                    IsSuccess = false,
                };
            }
            var result = await _userManger.CheckPasswordAsync(user, model.Password);
            if (!result)
            {
                return new UserManagerResponse
                {
                    Message = "Invalid password",
                    IsSuccess = false,
                };
            }
            var claims = new[]
            {
                new Claim("Email", model.Email),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
            var token = new JwtSecurityToken(
                issuer: _configuration["AuthSettings:Issuer"],
                audience: _configuration["AuthSettings:Audience"],
                claims: claims,
                expires: DateTime.Now.AddDays(30),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
            return new UserManagerResponse
            {
                Message = tokenAsString,
                IsSuccess = true,
                ExpireDate = token.ValidTo
            };
        }

        // Đăng xuất.
        public async Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model)
        {
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return new UserManagerResponse
                {
                    Message = "There is no user with that Email address",
                    IsSuccess = false,
                };
            }
            var result = await _userManger.CheckPasswordAsync(user, model.Password);
            if (!result)
            {
                return new UserManagerResponse
                {
                    Message = "Invalid password",
                    IsSuccess = false,
                };
            }
            var claims = new[]
            {
                new Claim("Email", model.Email),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
            };
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
            var token = new JwtSecurityToken(
                issuer: _configuration["AuthSettings:Issuer"],
                audience: _configuration["AuthSettings:Audience"],
                claims: claims,
                expires: DateTime.Now.AddDays(30),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
            return new UserManagerResponse
            {
                Message = tokenAsString,
                IsSuccess = true,
                ExpireDate = token.ValidTo
            };
        }

        public async Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token)
        {
            var user = await _userManger.FindByIdAsync(userId);
            if (user == null)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "User not found" };
            }
            var decodedToken = WebEncoders.Base64UrlDecode(token);
            string normalToken = Encoding.UTF8.GetString(decodedToken);
            var result = await _userManger.ConfirmEmailAsync(user, normalToken);
            if (result.Succeeded)
            {
                return new UserManagerResponse { Message = "Email confirmed successfully!", IsSuccess = true };
            }
            return new UserManagerResponse
            {
                IsSuccess = false,
                Message = "Email did not confirm",
                Errors = result.Errors.Select(e => e.Description)
            };
        }

        public async Task<UserManagerResponse> ForgetPasswordAsync(string email)
        {
            var user = await _userManger.FindByEmailAsync(email);
            if (user == null)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
            }
            var token = await _userManger.GeneratePasswordResetTokenAsync(user);
            var encodedToken = Encoding.UTF8.GetBytes(token);
            var validToken = WebEncoders.Base64UrlEncode(encodedToken);
            string url = $"{_configuration["AppUrl"]}/ResetPassword?email={email}&token={validToken}";
            await _mailService.SendEmailAsync(email, "Reset Password", "<h1>Follow the instructions to reset your password</h1>" +
                $"<p>To reset your password <a href='{url}'>Click here</a></p>");
            return new UserManagerResponse
            {
                IsSuccess = true,
                Message = "Reset password URL has been sent to the email successfully!"
            };
        }

        public async Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model)
        {
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
            }
            if (model.NewPassword != model.ConfirmPassword)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
            }
            var decodedToken = WebEncoders.Base64UrlDecode(model.Token);
            string normalToken = Encoding.UTF8.GetString(decodedToken);
            var result = await _userManger.ResetPasswordAsync(user, normalToken, model.NewPassword);
            if (result.Succeeded)
            {
                return new UserManagerResponse { Message = "Password has been reset successfully!", IsSuccess = true };
            }
            return new UserManagerResponse
            {
                Message = "Something went wrong",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            };
        }
    
        public async Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model)
        {
            var tokenString = model.Token;            
            var jwtEncodedString = tokenString.Substring(7); // trim 'Bearer ' from the start since its just a prefix for the token string
            var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
            string email = token.Claims.First(c => c.Type == "Email").Value;
            Console.WriteLine("email => " + email);
            var user = await _userManger.FindByEmailAsync(email);
            if (user == null)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
            }
            if (model.NewPassword != model.ConfirmPassword)
            {
                return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
            }
            var token2 = await _userManger.GeneratePasswordResetTokenAsync(user);
            var result = await _userManger.ResetPasswordAsync(user, token2, model.NewPassword);
            if (result.Succeeded)
            {
                return new UserManagerResponse { Message = "Password has been changed successfully!", IsSuccess = true };
            }
            return new UserManagerResponse
            {
                Message = "Something went wrong",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            };
        }     
    }

}

File appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=foo;User Id=sa; Password=SecrEt_STring;Trusted_Connection=False;MultipleActiveResultSets=True"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "AuthSettings": {
    "Key": "This is the key that we will use in the encryption",
    "Audience": "http://example.io",
    "Issuer": "http://example.io"
  },
  "SendGridAPIKey": "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",
  "AppUrl": "http://localhost:5002"
}

In controller

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using shadow.Data;
using shadow.DTO;
using shadow.Models;
using shadow.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace shadow.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class AssetItemController : ControllerBase
    {
        private IUserService _userService;
        private IMailService _mailService;
        private IConfiguration _configuration;
        private ApplicationDbContext _db;

        public AssetItemController(IUserService userService, IMailService mailService, IConfiguration configuration, ApplicationDbContext context /*, SignInManager<IdentityUser> signInManager */)
        {
            _userService = userService;
            _mailService = mailService;
            _configuration = configuration;
            _db = context;
            // _signInManager = signInManager;
        }

        // Admin liệt kê tất cả các tài sản.
        [HttpGet]
        [Route("all")]
        [Authorize(Roles ="ADMIN")]        
        public async Task<ActionResult<AssetItem>> GetAllAssetItems()
        {
            var a = await Task.Run(() => _db.AssetItems);
            if (a != null)
            {
                return Ok(a.ToList());
            }
            else
            {
                return NoContent();
            }
        }

        // Liệt kê tất cả các tài sản theo user_id.
        [HttpGet]
        [Route("user/{userId}")]
        public async Task<ActionResult<IEnumerable<AssetItem>>> GetAllAssetItemsByUserId(string userId)
        {
            var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.UserId == userId).ToList());
            if (assetListByUserId != null)
            {
                return Ok(assetListByUserId);
            }
            else
            {
                return NoContent();
            }

        }

        // Xem chi tiet asset_item theo id cua no.
        [HttpGet]
        [Route("{assetItemId}")]
        public async Task<ActionResult<IEnumerable<AssetItem>>> ViewAssetItemById(int assetItemId)
        {
            var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.Id == assetItemId).FirstOrDefault());
            if (assetListByUserId != null)
            {
                return Ok(assetListByUserId);
            }
            else
            {
                return NoContent();
            }
        }

        // Xóa tài sản.
        [HttpDelete("{id}")]
        public async Task<ActionResult<string>> DeleteAssetItem(int id)
        {
            var item = _db.AssetItems.Find(id);
            if (item != null)
            {
                _db.AssetItems.Remove(item);
                await _db.SaveChangesAsync();
                return Ok("ok");
            }
            else
            {
                return NotFound();
            }

        }

        // POST http://localhost:5002/AssetItem
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
        [Authorize]
        public async Task<ActionResult<AssetItem>> Add([FromForm] AssetItemDTO input)
        {
            if (ModelState.IsValid)
            {
                AssetItem assetItem = new AssetItem();
                if (input.AssetTypeId != null)
                {
                    assetItem.AssetTypeId = input.AssetTypeId;
                }

                if (input.UserId != null)
                {
                    assetItem.UserId = input.UserId;
                }

                if (input.TrustedPersonId != null)
                {
                    assetItem.TrustedPersonId = input.TrustedPersonId;
                }

                if (input.TrustedContent != null)
                {
                    assetItem.TrustedContent = input.TrustedContent;
                }

                if (input.Description != null)
                {
                    assetItem.Description = input.Description;
                }

                assetItem.Created = DateTime.Now;
                if (input.file != null)
                {
                    if (IsImageFile(input.file))
                    {
                        string imgPath = await WriteFile(input.file);
                        assetItem.ImagePath = imgPath;
                    }
                    else
                    {
                        return BadRequest(new { message = "Invalid file extension" });
                    }
                }
                _db.AssetItems.Add(assetItem);
                await _db.SaveChangesAsync();
                return Ok(assetItem);
            }
            else
            {
                return BadRequest(ModelState);
            }
        }

        // Edit 1 tài sản.
        // PUT http://localhost:5002/AssetItem
        [HttpPut("{id}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
        [Authorize]
        public async Task<ActionResult<AssetItem>> Edit([FromForm] AssetItemDTO input, int id)
        {
            if (ModelState.IsValid)
            {
                var foo = _db.AssetItems.Find(id);
                if (!String.IsNullOrEmpty(input.TrustedContent))
                {
                        foo.TrustedContent = input.TrustedContent;                    
                }else              
                {
                    foo.TrustedContent = null;
                }

                if (input.file != null)
                {
                    string imgPath = await WriteFile(input.file);
                    foo.ImagePath = imgPath;
                }
                else
                {
                    foo.ImagePath = null;
                }

                if (!String.IsNullOrEmpty(input.Description))
                {
                    foo.Description = input.Description;
                }
                else
                {
                    foo.Description = null;
                }

                foo.AssetTypeId = input.AssetTypeId;

                if (input.TrustedPersonId != null)
                {
                    foo.TrustedPersonId = input.TrustedPersonId;
                }
                // Hiện chưa ghi nhận user_modified. Cần xem lại Requirement.
                if (!String.IsNullOrEmpty(input.UserModified))
                {
                    foo.UserModified = input.UserModified;
                }
                else
                {
                    foo.UserModified = null;
                }
                foo.Modified = DateTime.Now;
                _db.Update(foo);
                await _db.SaveChangesAsync();
                return Ok(foo);
            }
            else
            {
                return BadRequest(ModelState);
            }
        }

        private bool IsImageFile(IFormFile file)
        {
            try
            {
                var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
                return (extension == ".png" || extension == ".jpg" || extension == ".bmp" || extension == ".gif" || extension == ".tif");
            }
            catch
            {
                return false;
            }
        }

        private async Task<string> WriteFile(IFormFile file)
        {
            string fileName = "";
            try
            {
                var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
                fileName = DateTime.Now.Ticks + extension; //Create a new Name for the file due to security reasons.
                var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\file");
                if (!Directory.Exists(pathBuilt))
                {
                    Directory.CreateDirectory(pathBuilt);
                }
                var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files", fileName);           
                using (var stream = new FileStream(path, FileMode.Create))
                {
                    await file.CopyToAsync(stream);
                }
                return fileName;
            }
            catch (Exception e)
            {
                //
            }
            return fileName;
        }
    }

}

Database

select * from AspNetUsers;
select * from AspNetUserRoles;
select * from AspNetRoles;

result

enter image description here

When add [Authorize(Roles ="ADMIN")] , it shows 403 Forbidden .

enter image description here How to make Authorize by Role work?

Do Nhu Vy
  • 33,131
  • 37
  • 143
  • 202

1 Answers1

3

You are not actually adding roles to the token. Each role should be a claim, like this.

new Claim(ClaimTypes.Role, "<role name">));

Eg.

var claims = new[]
{
      new Claim("Email", model.Email),
      new Claim(ClaimTypes.NameIdentifier, user.Id),
};

var roles = await _userManger.GetRolesAsync(user); // note: you have a typo in "_userManger"

var claimsWithRoles = roles.Select(role => new Claim(ClaimTypes.Role, role));
var allClaims = claims.Concat(claimsWithRoles);

Then add allClaims to the JWT, like this:

var token = new JwtSecurityToken(
            issuer: _configuration["AuthSettings:Issuer"],
            audience: _configuration["AuthSettings:Audience"],
            claims: allClaims , // note how we add all claims, including the ones for roles, here
            expires: DateTime.Now.AddDays(30),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

Let me know how it works.

Edit: Sorry, forgot AddRange returns void;

Riwen
  • 2,158
  • 1
  • 11
  • 20
  • I catch error https://user-images.githubusercontent.com/1328316/94257777-0850ba00-ff56-11ea-9cc3-150801761553.png – Do Nhu Vy Sep 25 '20 at 10:39
  • Fixed it. Check again, please. – Riwen Sep 25 '20 at 10:43
  • try `claims.AddRange(roles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role)));` – Farshan Sep 25 '20 at 10:47
  • I catch error https://user-images.githubusercontent.com/1328316/94385639-dca91c00-016f-11eb-85a0-3316a8cf44a0.png – Do Nhu Vy Sep 28 '20 at 02:49
  • @Farshan I catch error https://user-images.githubusercontent.com/1328316/94387015-1af40a80-0173-11eb-8fb3-235754277f63.png – Do Nhu Vy Sep 28 '20 at 03:12
  • @DoNhuVy fixed it, check again please. – Riwen Sep 28 '20 at 09:08
  • this is my token https://gist.github.com/donhuvy/1135025ba9e4702222e12254fe2a6fa6 – Do Nhu Vy Sep 28 '20 at 10:07
  • it still show Invalid signature: https://user-images.githubusercontent.com/1328316/94419540-22d0a080-01ad-11eb-968d-89fcb8c3a9d5.png – Do Nhu Vy Sep 28 '20 at 10:08
  • 1
    @DoNhuVy that shouldn't be a problem, I think. You need to use your actual 256-bit secret to verify the signature, instead of the preset `your-256-bit-secret` value. There is generally no need to do that in the debugger, but you can paste your key and test it. However, your token now contains the appropriate roles, so you should be able to use the endpoints. Quick note: may want to remove the Sendgrid API key from the `appsettings.json` code in the main question. – Riwen Sep 28 '20 at 10:42