openiddict / openiddict-core

Flexible and versatile OAuth 2.0/OpenID Connect stack for .NET
https://openiddict.com/
Apache License 2.0
4.43k stars 520 forks source link

Upgrading to latest version of OpenIdDict, mobile api resource server running .net 4.6.1 says 401 Unauthorized #1338

Closed mitchellgarner closed 3 years ago

mitchellgarner commented 3 years ago

We are updating to the latest version of OpenIdDict and are running into a challenge with one of our .net framework 4.6.1 web applications. This application uses authorization code flow to get the token from the openiddict idp. The mobile app also does this using the same application registration in openiddict. Mobile then attaches the access token to calls to the api back-end.

Authentication done by the web api calling to openiddict is successful, however calls from the mobile app result only in a 401 unauthorized.

I initially thought this was due to not including all required claims in the access token, however upon inspected the production system's token returned claims are the same.

I am under the impression validation within the api application may not be set correctly, but it does not look different than what I have found in documentation, and I haven't changed it from our current production version's settings.

Here is the configuration of our .net framework application's validation:

`var jwtFormat = new JwtFormat( new TokenValidationParameters { AuthenticationType = "Bearer", ValidIssuer = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.Authority"], NameClaimType = "sub", RoleClaimType = System.Security.Claims.ClaimTypes.Role, ValidateIssuerSigningKey = true, ValidateAudience = false, IssuerSigningKey = new InMemorySymmetricSecurityKey( Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.JWTSigningKey"])) }) { TokenHandler = new CustomJwtSecurityTokenHandler(IocManager.Instance) };

            app.UseOAuthBearerAuthentication(
                new OAuthBearerAuthenticationOptions
                {
                    AccessTokenFormat  = jwtFormat,
                    AuthenticationMode = AuthenticationMode.Active
                });`

and Authentication configuration:

`app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Index"), Provider = IocManager.Instance.Resolve(), });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        if (IsTrue("ExternalAuth.SevanIdentity.IsEnabled"))
        {
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions("SevanIdentity")
                {
                    AuthenticationMode         = AuthenticationMode.Passive,
                    SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
                    ClientId                   = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.ClientId"],
                    ClientSecret               = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.ClientSecret"],
                    Authority                  = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.Authority"],
                    RedirectUri                = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.RedirectUri"],
                    PostLogoutRedirectUri      = ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.PostLogoutRedirectUri"],
                    Scope                      = OpenIdConnectScopes.OpenIdProfile + " email roles",
                    ResponseType               = OpenIdConnectResponseTypes.IdToken,
                    Caption                    = "SevanIdentity",
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        IssuerSigningKey = new InMemorySymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigurationManager.AppSettings["ExternalAuth.SevanIdentity.JWTSigningKey"])),
                        ValidateIssuer = true
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthenticationFailed = OnSevanIdentityAuthenticationFailed,

                        RedirectToIdentityProvider = notification =>
                        {
                            if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
                            {
                                notification.ProtocolMessage.PostLogoutRedirectUri =
                                    $"{notification.Request.Scheme}://{notification.Request.Host}/Mpa/Dashboard";
                            }
                            else
                            {
                                notification.ProtocolMessage.RedirectUri =
                                    $"{notification.Request.Scheme}://{notification.Request.Host}/signin-sevanidentity";
                            }

                            return Task.CompletedTask;
                        }
                    }
                });
        }`

I am unsure if related, but since upgrading to openiddict 3.1 tokens are being labeled as "Invalid Signature" when put into jwt.io. Here is our current configuration for our IdP running openiddict.

`public void ConfigureServices(IServiceCollection services) { Logger.LogError("Starting");

        services.AddCors(options => {
            options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
        });

        services.AddDataProtection()
                .PersistKeysToAzureBlobStorage(
                     Microsoft.Azure.Storage.CloudStorageAccount.Parse(Configuration.GetConnectionString("KeyStorage"))
                                        .CreateCloudBlobClient()
                                        .GetContainerReference("identity-key-store"),
                     "keys.xml");

        services.AddControllersWithViews(
            options =>
            {
                options.Filters.Add(new ChangePassworFilterAttribute());
            });

        services.AddTransient(typeof(ILogWrapper<>), typeof(LogWrapper<>));

        services.AddSwaggerGen(
            c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo {Title = "Identity API", Version = "v1"});

                // Set the comments path for the Swagger JSON and UI.
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
            });

        services.AddDbContext<ApplicationDbContext>(
            options =>
            {
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection"));

                options.UseOpenIddict<IdentityApplication, IdentityAuthorization, OpenIddictEntityFrameworkCoreScope, IdentityToken, string>();
            });

        services.AddScoped(typeof(IRepo<>), typeof(Repo<>));
        services.AddScoped<IPasswordValidator<ApplicationUser>, ApplicationPasswordValidator<ApplicationUser>>();

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

        services.Configure<IdentityOptions>(
            options =>
            {
                options.ClaimsIdentity.UserNameClaimType = Claims.Name;
                options.ClaimsIdentity.UserIdClaimType   = Claims.Subject;
                options.ClaimsIdentity.RoleClaimType     = Claims.Role;
            });

        // OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
        // (like pruning orphaned authorizations/tokens from the database) at regular intervals.
        services.AddQuartz(options =>
        {
            options.UseMicrosoftDependencyInjectionJobFactory();
            options.UseSimpleTypeLoader();
            options.UseInMemoryStore();
        });

        // Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
        services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);

        services.AddAuthentication()
                .AddJwtBearer(
                     options =>
                     {
                         options.Authority = Configuration["Self"];
                         options.TokenValidationParameters = new TokenValidationParameters
                         {
                             IssuerSigningKeys = new List<SecurityKey>
                             {
                                 new X509SecurityKey(
                                     new X509Certificate2(
                                         "signingkey.pfx",
                                         "s3v4n",
                                         X509KeyStorageFlags.UserKeySet)),
                                 new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTSigningKey"]))
                             },
                             ValidateIssuer = true,
                             ValidateAudience = false,
                             RequireSignedTokens = true,
                         };
                     }
                     )
                .AddOpenIdConnect(
                     "Microsoft",
                     "Microsoft",
                     options =>
                     {
                         options.ClientId             = Configuration["External:Microsoft:ClientId"];
                         options.ResponseType         = OpenIdConnectResponseType.IdToken;
                         options.Authority            = Configuration["External:Microsoft:Authority"];
                         options.SignedOutRedirectUri = Configuration["External:Microsoft:SignOutUri"];
                         options.CallbackPath         = "/signin-microsoft";
                         options.Scope.Clear();
                         options.Scope.Add("openid");
                         options.Events = new OpenIdConnectEvents
                         {
                             OnRemoteFailure = context =>
                             {
                                 var returnUrl = "/";

                                 if (context.Request.Form.TryGetValue("state", out var stringValues))
                                 {
                                     if (context.Options is OpenIdConnectOptions oidcOptions)
                                     {
                                         var props     = oidcOptions.StateDataFormat.Unprotect(stringValues);
                                         var fragments = props?.RedirectUri.Split("?");

                                         if (fragments?.Length > 1)
                                         {
                                             var tmp = HttpUtility.ParseQueryString(fragments[1])["returnUrl"];

                                             if (!string.IsNullOrWhiteSpace(tmp))
                                             {
                                                 returnUrl = tmp;
                                             }
                                         }
                                     }
                                 }

                                 context.HandleResponse();
                                 Logger.LogWarning(2000, $"Microsoft login failed: {context.Failure.Message}");
                                 context.Response.Redirect(returnUrl);
                                 return Task.CompletedTask;
                             },
                             OnRedirectToIdentityProvider = context =>
                             {
                                 if (context.Properties.Items.TryGetValue("login_hint", out var loginHint))
                                 {
                                     context.ProtocolMessage.LoginHint = loginHint;
                                 }

                                 return Task.CompletedTask;
                             }
                         };
                         options.TokenValidationParameters = new TokenValidationParameters
                         {
                             ValidateIssuer = false,
                             NameClaimType  = "name"
                         };
                     });

        services.ConfigureExternalCookie(
            options => { options.Cookie.SameSite = SameSiteMode.None; });

        services.ConfigureApplicationCookie(
            options => { options.Cookie.SameSite = SameSiteMode.None; });

        services.AddOpenIddict()
                .AddCore(
                     options =>
                     {
                         options.UseEntityFrameworkCore()
                                .UseDbContext<ApplicationDbContext>()
                                .ReplaceDefaultEntities<IdentityApplication, IdentityAuthorization, OpenIddictEntityFrameworkCoreScope, IdentityToken, string>();
                     })
                .AddServer(
                     options =>
                     {
                     options.SetAuthorizationEndpointUris("/connect/authorize")
                            .SetLogoutEndpointUris("/connect/logout")
                            .SetTokenEndpointUris("/connect/token")
                            .SetUserinfoEndpointUris("/api/userinfo")
                            .SetIntrospectionEndpointUris("/connect/introspect");

                         var cert = new X509Certificate2("signingkey.pfx", "*****", X509KeyStorageFlags.UserKeySet);
                         options.AddSigningCertificate(cert);

                         options.AddSigningKey(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTSigningKey"])));
                         options.AddEncryptionCertificate(cert);
                         options.AllowAuthorizationCodeFlow()
                                .AllowImplicitFlow()
                                .AllowPasswordFlow()
                                .AllowClientCredentialsFlow()
                                .AllowRefreshTokenFlow()
                                .AllowHybridFlow();

                         options.RegisterScopes(
                             Scopes.Email,
                             Scopes.Profile,
                             Scopes.Roles);

                         options.UseAspNetCore()
                            .EnableAuthorizationEndpointPassthrough()
                            .EnableLogoutEndpointPassthrough()
                            .EnableUserinfoEndpointPassthrough()
                            .EnableStatusCodePagesIntegration()
                            .EnableTokenEndpointPassthrough()
                            .EnableVerificationEndpointPassthrough();
                         options.DisableAccessTokenEncryption();

                         options.SetAccessTokenLifetime(TimeSpan.FromDays(14));
                         // Enable Quartz.NET integration. - haven't got this to work yet, but all libraries are added
                         //options.UseQuartz();
                     })
                .AddValidation(options =>
                {
                    options.SetIssuer(Configuration["Self"]);
                    options.UseSystemNetHttp();
                    options.Configure(options => options.TokenValidationParameters.IssuerSigningKey =
                        new SymmetricSecurityKey(
                                Encoding.UTF8.GetBytes(Configuration["JWTSigningKey"])
                            )
                        );
                    options.UseAspNetCore();
                });

        if (HostingEnvironment.IsDevelopment())
        {
            services.AddTransient<IEmailSender, LoggingEmailSender>();

        }
        else
        {
            services.AddTransient<IEmailSender, EmailSender>();
            services.Configure<SmtpOptions>(Configuration.GetSection("Smtp"));
            services.AddTransient<IMailTransport, SmtpClient>();
        }

        services.AddApplicationInsightsTelemetry();
        services.AddTransient<IMailTemplates, MailTemplates>();
        services.AddSingleton<IBackgroundJobQueue, BackgroundJobQueue>();
        services.AddSingleton<IHostedService, BackgroundJobService>();
    }`

Our implementation utilizes client credentials, implicit (w resource server & needed introspection), and code flow. (password of cert is removed)

Here is our Authorization controller as well. Each part of this seems to work except logout which i have not yet looked at.

`namespace SevanIdentity.Controllers { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; using OpenIddict.Core; using OpenIddict.EntityFrameworkCore.Models; using OpenIddict.Server.AspNetCore; using SevanIdentity.Models; using SevanIdentity.Models.AuthorizationViewModel; using static OpenIddict.Abstractions.OpenIddictConstants;

[ApiExplorerSettings(IgnoreApi = true)]
public class AuthorizationController : Controller
{
    private readonly OpenIddictApplicationManager<IdentityApplication> _applicationManager;
    private readonly IOpenIddictAuthorizationManager _authorizationManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly OpenIddictScopeManager<OpenIddictEntityFrameworkCoreScope> _scopeManager;

    public AuthorizationController(
        OpenIddictApplicationManager<IdentityApplication> applicationManager,
        IOpenIddictAuthorizationManager authorizationManager,
        SignInManager<ApplicationUser> signInManager,
        UserManager<ApplicationUser> userManager,
        OpenIddictScopeManager<OpenIddictEntityFrameworkCoreScope> scopeManager)
    {
        _applicationManager = applicationManager;
        _authorizationManager = authorizationManager;
        _signInManager = signInManager;
        _userManager = userManager;
        _scopeManager = scopeManager;
    }

    #region Authorization code, implicit and implicit flows
    // Note: to support interactive flows like the code flow,
    // you must provide your own authorization endpoint action:
    [AllowAnonymous]
    [HttpGet("~/connect/authorize")]
    [HttpPost("~/connect/authorize")]
    public async Task<IActionResult> Authorize()
    {
        var request = HttpContext.GetOpenIddictServerRequest() ??
            throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");

        if (!User.Identity.IsAuthenticated)
        {
            var returnUrl = Request.PathBase + Request.Path + Request.QueryString;
            return RedirectToAction("Login", "Account", new {returnUrl, request.ClientId});
        }

        //Debug.Assert(request.IsAuthorizationCodeFlow(),
        //    "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
        //    "Make sure services.AddOpenIddict().AddServer().UseMvc() is correctly called.");

        // Retrieve the application details from the database.
        var applicationList = _applicationManager.ListAsync(x => x.Include(y => y.ApplicationRoles)
                                                               .ThenInclude(z => z.Role)
                                                               .Include(y => y.ApplicationUsers)
                                                               .ThenInclude(z => z.User)
                                                               );
        List<IdentityApplication> applications = new List<IdentityApplication>();

        await foreach(var a in applicationList)
        {
            applications.Add(a);
        }
        IdentityApplication application = applications.SingleOrDefault(x => x.ClientId == request.ClientId);

        if (application == null)
        {
            return View(
                "Error",
                new ErrorViewModel
                {
                    RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                    Errors = new List<Error>
                    {
                        new Error
                        {
                            Code = Errors.InvalidClient,
                            Description =
                                "Details concerning the calling client application cannot be found in the database"
                        }
                    }
                });
        }

        var user = await _userManager.GetUserAsync(User);

        if (user == null)
        {
            return View(
                "Error",
                new ErrorViewModel
                {
                    RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
                    Errors = new List<Error>
                    {
                        new Error
                        {
                            Code        = Errors.ServerError,
                            Description = "An internal error has occurred"
                        }
                    }
                });
        }

        if (application.ApplicationUsers.All(x => x.UserId != user.Id))
        {
            var found = false;

            foreach (var role in application.ApplicationRoles)
            {
                if (await _userManager.IsInRoleAsync(user, role.Role.Name))
                {
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                // Notify OpenIddict that the authorization grant has been denied by the resource owner
                // to redirect the user agent to the client application using the appropriate response_mode.
                return StatusCode(403);
            }
        }

        var principal = await CreateClaimsPrincipalAsync(request, user);

        // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
        return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    }

    // Note: the logout action is only useful when implementing interactive
    // flows like the authorization code flow or the implicit flow.
    [HttpGet("/connect/logout")]
    [HttpPost("/connect/logout"), ValidateAntiForgeryToken]
    public async Task<IActionResult> Logout()
    {
        if (HttpContext.Request.Method == "GET")
        {
            OpenIddictRequest request = HttpContext.GetOpenIddictServerRequest() ??
            throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
            Debug.Assert(request.IsNoneFlow(),
                "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                "Make sure services.AddOpenIddict().AddServer().UseMvc() is correctly called.");

            return await Logout();
        }
        else
        {
            // Ask ASP.NET Core Identity to delete the local and external cookies created
            // when the user agent is redirected from the external identity provider
            // after a successful authentication flow (e.g Google or Facebook).
            await _signInManager.SignOutAsync();

            // Returning a SignOutResult will ask OpenIddict to redirect the user agent
            // to the post_logout_redirect_uri specified by the client application.
            return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

    }
    #endregion

    #region Password, authorization code and refresh token flows
    // Note: to support non-interactive flows like password,
    // you must provide your own token endpoint action:

    [HttpPost("/connect/token"), Produces("application/json")]
    public async Task<IActionResult> Exchange()
    {
        OpenIddictRequest request = HttpContext.GetOpenIddictServerRequest() ??
            throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
        Debug.Assert(request.IsClientCredentialsGrantType(),
            "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
            "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

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

            // Validate the username/password parameters and ensure the account is not locked out.
            var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: true);
            if (!result.Succeeded)
            {
                return BadRequest(new OpenIddictResponse
                {
                    Error = Errors.InvalidGrant,
                    ErrorDescription = "The username/password couple is invalid."
                });
            }

            // Create a new authentication ticket.
            var principal = await CreateClaimsPrincipalAsync(request, user);
            await UpdateUserLastLogInTime(user);
            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
        // testing addition here
        else if (request.IsClientCredentialsGrantType())
        {
            // Note: the client credentials are automatically validated by OpenIddict:
            // if client_id or client_secret are invalid, this action won't be invoked.

            var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
            if (application == null)
            {
                throw new InvalidOperationException("The application details cannot be found in the database.");
            }

            // Create a new ClaimsIdentity containing the claims that
            // will be used to create an id_token, a token or a code.
            var identity = new ClaimsIdentity(
                TokenValidationParameters.DefaultAuthenticationType,
                Claims.Name, Claims.Role);

            // Use the client_id as the subject identifier.
            identity.AddClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application),
                Destinations.AccessToken, Destinations.IdentityToken);

            identity.AddClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application),
                Destinations.AccessToken, Destinations.IdentityToken);

            // Note: In the original OAuth 2.0 specification, the client credentials grant
            // doesn't return an identity token, which is an OpenID Connect concept.
            //
            // As a non-standardized extension, OpenIddict allows returning an id_token
            // to convey information about the client application when the "openid" scope
            // is granted (i.e specified when calling principal.SetScopes()). When the "openid"
            // scope is not explicitly set, no identity token is returned to the client application.

            // Set the list of scopes granted to the client application in access_token.
            var principal = new ClaimsPrincipal(identity);
            principal.SetScopes(request.GetScopes());
            principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());

            foreach (var claim in principal.Claims)
            {
                claim.SetDestinations(GetDestinations(claim));
            }

            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }
        else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
        {
            // Retrieve the claims principal stored in the authorization code/refresh token.
            var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

            // Retrieve the user profile corresponding to the authorization code/refresh token.
            // Note: if you want to automatically invalidate the authorization code/refresh token
            // when the user password/roles change, use the following line instead:
            // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
            var user = await _userManager.GetUserAsync(info.Principal);
            if (user == null)
            {
                return BadRequest(new OpenIddictResponse
                {
                    Error = Errors.InvalidGrant,
                    ErrorDescription = "The token is no longer valid."
                });
            }

            // Ensure the user is still allowed to sign in.
            if (!await _signInManager.CanSignInAsync(user))
            {
                return BadRequest(new OpenIddictResponse
                {
                    Error = Errors.InvalidGrant,
                    ErrorDescription = "The user is no longer allowed to sign in."
                });
            }

            // Create a new authentication ticket, but reuse the properties stored in the
            // authorization code/refresh token, including the scopes originally granted.
            var principal = await CreateClaimsPrincipalAsync(request, user, info.Properties);
            await UpdateUserLastLogInTime(user);
            return SignIn(principal, info.Properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

        return BadRequest(new OpenIddictResponse
        {
            Error = Errors.UnsupportedGrantType,
            ErrorDescription = "The specified grant type is not supported."
        });
    }
    #endregion

    private IEnumerable<string> GetDestinations(Claim claim)
    {
        // Note: by default, claims are NOT automatically included in the access and identity tokens.
        // To allow OpenIddict to serialize them, you must attach them a destination, that specifies
        // whether they should be included in access tokens, in identity tokens or in both.

        return claim.Type switch
        {
            Claims.Name or
            Claims.Subject or
            Claims.Role or
            Claims.Username or
            Claims.Email or
            Claims.Audience or
            Claims.TokenUsage or
            Claims.Nonce or
            Claims.AccessTokenHash
                => ImmutableArray.Create(Destinations.AccessToken, Destinations.IdentityToken),

            _ => ImmutableArray.Create(Destinations.AccessToken),
        };
    }

    private async Task<ClaimsPrincipal> CreateClaimsPrincipalAsync(
        OpenIddictRequest request, ApplicationUser user,
        AuthenticationProperties properties = null)
    {
        // Create a new ClaimsPrincipal containing the claims that
        // will be used to create an id_token, a token or a code.
        var principal = await _signInManager.CreateUserPrincipalAsync(user);

        var scopes = request.GetScopes();
        var immutableScopes = ImmutableArray.ToImmutableArray(scopes);

        if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
        {
            // Note: in this sample, the granted scopes match the requested scope
            // but you may want to allow the user to uncheck specific scopes.
            // For that, simply restrict the list of scopes before calling SetScopes.
            principal.SetScopes(request.GetScopes());
            principal.SetResources(await _scopeManager.ListResourcesAsync(immutableScopes).ToListAsync());
        }

        foreach (var claim in principal.Claims)
        {
            claim.SetDestinations(GetDestinations(claim));
        }

        return principal;
    }

    private async Task UpdateUserLastLogInTime(ApplicationUser user)
    {
        user.LastLogInTime = DateTimeOffset.UtcNow;
        await _userManager.UpdateAsync(user);
    }
}

}`

Any ideas would be appreciated. All other flows are working as expected after adding required rst entries to application permissions.

kevinchalet commented 3 years ago

Hi,

OpenIddict 3.0 comes with a dedicated validation stack that works with JWT and Data Protection access tokens. It uses a modern JWT stack and is natively compatible with ASP.NET 4.x via OWIN. Consider replacing the JWT/app.UseOAuthBearerAuthentication() middleware by OpenIddict's validation middleware, as it's the recommended option in recent versions of OpenIddict.