0

In my .net core 2.2 MVC Web Api application, I have a Token endpoint for my Angular client app. I'm using OpenIddictCore for Authentication. I'm not using JWT tokens, I'm using default opaque tokens with OpenIddict and I want to use Roles and Claims to guard routes on my angular app. When I request Token from my Token endpoint of Web Api I get this result:

{
"token_type":"Bearer",
"access_token":"{access token here}",
"expires_in":3600
}

which does not provides me an information of roles. Is there any proper way to get a JSON response like this:

{
"token_type":"Bearer",
"access_token":"{access token here}",
"expires_in":3600,
"roles":["ResourceEditor","ResourceViewer"... etc]
}

I have searched for this a lot but I couldn't found anything about it. Since I'm looking for the solution for a long time.

Everything works as expected with authorization, everything works fine. my startup configurations are:

services.AddDbContext<xxDbContext>(opt =>
    {
        opt.UseOpenIddict<Guid>();
        opt.UseSqlServer(
            @"Server=xxServer;Database=xxDB;User Id=xxUser;Password=xxPassword");
    });
services.Configure<IdentityOptions>(opt =>
    {
        opt.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
        opt.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
        opt.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
        opt.Password.RequireDigit = true;
        opt.Password.RequiredLength = 8;
        opt.Password.RequireLowercase = true;
        opt.Password.RequireUppercase = true;
        opt.Password.RequireNonAlphanumeric = false;    
        //opt.SignIn.RequireConfirmedEmail = true;
    });

services.AddIdentity<UserEntity, UserRoleEntity>()
    .AddEntityFrameworkStores<xxDbContext>()
    .AddDefaultTokenProviders();

// Add OpenIddict services
services.AddOpenIddict(opt =>
    {
        opt.AddCore(conf => { conf.UseEntityFrameworkCore().UseDbContext<xxDbContext>(); });
        opt.AddServer(conf =>
        {
            conf.UseMvc();
            conf.EnableTokenEndpoint("/api/token");
            conf.AllowPasswordFlow();
            conf.AcceptAnonymousClients();
            conf.RegisterClaims(OpenIdConnectConstants.Claims.Role,OpenIdConnectConstants.Claims.Username);
            //conf.UseJsonWebTokens();
            //this doesn't works so I keep going with opaque tokens
            //conf.AddSigningCertificate(
            //    assembly: typeof(Startup).GetTypeInfo().Assembly,
            //    resource: "path to .pfx",
            //    password: "secret");
        });
        opt.AddValidation();
    });

services.AddAuthentication(options =>
    {
        options.DefaultScheme = OpenIddictValidationDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = OpenIddictValidationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIddictValidationDefaults.AuthenticationScheme;
    });

and my token endpoint is:

    [Route("api/[controller]")]
    public class TokenController : Controller
    {
        private readonly IOptions<IdentityOptions> _identityOptions;
        private readonly SignInManager<UserEntity> _signInManager;
        private readonly UserManager<UserEntity> _userManager;

        public TokenController(IOptions<IdentityOptions> identityOptions, SignInManager<UserEntity> signInManager,
            UserManager<UserEntity> userManager)
        {
            _identityOptions = identityOptions;
            _signInManager = signInManager;
            _userManager = userManager;
        }

        [HttpPost(Name = nameof(TokenExchangeAsync))]
        public async Task<IActionResult> TokenExchangeAsync(
            OpenIdConnectRequest request,
            CancellationToken ct)
        {
            if (!request.IsPasswordGrantType())
            {
                return BadRequest(new OpenIdConnectResponse()
                {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The specified grant type is not supported"
                });
            }

            var user = await _userManager.FindByNameAsync(request.Username);
            if (user == null)
                return BadRequest(new OpenIdConnectResponse()
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The username or password is invalid"
                });


            if (!await _signInManager.CanSignInAsync(user))
                return BadRequest(new OpenIdConnectResponse()
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The specified user is not allowed to sign in"
                });

            // Ensure the user is not already locked out
            if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user))
            {
                return BadRequest(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The username or password is invalid."
                });
            }


            if (!await _userManager.CheckPasswordAsync(user, request.Password))
            {
                if (_userManager.SupportsUserLockout)
                {
                    await _userManager.AccessFailedAsync(user);
                }

                return BadRequest(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "The username or password is invalid."
                });
            }



            if (_userManager.SupportsUserLockout)
            {
                await _userManager.ResetAccessFailedCountAsync(user);
            }

            // Create a new authentication ticket w/ the user identity
            var ticket = await CreateTicketAsync(request, user);

            return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
        }

        private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, UserEntity user)
        {
            var principal = await _signInManager.CreateUserPrincipalAsync(user);

            var ticket = new AuthenticationTicket(principal,
                new AuthenticationProperties(),
                OpenIdConnectServerDefaults.AuthenticationScheme);


            foreach (var claim in ticket.Principal.Claims)
            {

                if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) continue;

                claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
            }

            return ticket;
        }
    }

If this approach is wrong then please tell me. I just get stuck. Trying to implement this for hours and now i can't feel my brain.

vslzl
  • 349
  • 5
  • 17
  • 1
    https://stackoverflow.com/questions/40502600/how-can-i-add-custom-claims-to-be-returned-when-requesting-a-token-using-openidd/40504779#40504779 should put you on the right track. – Kévin Chalet Feb 26 '19 at 14:54
  • Thank you very much @Pinpoint it seems exactly what i want. i’ve also found a way upon the official samples but it is nice to see another proper way for who faces the same issue. I’ll post my solution when I get back to work. – vslzl Feb 26 '19 at 15:05

0 Answers0