简体   繁体   中英

Refresh token with OpenIddict returns always principal to null

Since the migration from OpenIdDict 2.x to 3.X, we are not able to use refreshtoken anymore. Our code is based on dotnet core 3.1

The processing of user/password works fine and the user receives his tokens (access, id and refresh)

But as soon as we want to send a refresh token, the process of getting the principal from the user returns a null.

var info = await HttpContext.AuthenticateAsync(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); var user = await _userManager.GetUserAsync(info.Principal);

The error message

System.ArgumentNullException: Value cannot be null. (Parameter 'principal')

Here is the code we use to bootstrap Openiddict

startup.cs

services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
            .UseDbContext<DataContext>();
    })
    .AddServer(options =>
    {
        options.SetTokenEndpointUris("/connect/token")
            .AllowPasswordFlow()
            .AllowRefreshTokenFlow();

        options.SetAccessTokenLifetime(TimeSpan.FromDays(1));
        options.SetRefreshTokenLifetime(TimeSpan.FromDays(1));
        options.AcceptAnonymousClients();
        options.AddDevelopmentEncryptionCertificate();
        options.AddDevelopmentSigningCertificate();
        options.RequireProofKeyForCodeExchange();

        options.UseAspNetCore()
            .EnableTokenEndpointPassthrough();
    })
    .AddValidation(options =>
    {
        options.UseLocalServer();
        options.UseAspNetCore();
    });

AuthorizationController.cs

[HttpPost("~/connect/token")]
public async Task<IActionResult> Login()
{
    var request = HttpContext.GetOpenIddictServerRequest() ??
        throw new InvalidOperationException(
            "The OpenID Connect request cannot be retrieved.");

    if (request.IsPasswordGrantType())
    {
        return await CheckPasswordGrantType(request);
    }

    if (request.IsRefreshTokenGrantType())
    {
        return await CheckRefreshTokenGrantType();
    }

    throw new NotImplementedException("The specified grant type is not implemented.");
}

private async Task<IActionResult> CheckRefreshTokenGrantType()
{
    var info = await HttpContext.AuthenticateAsync(
        OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);

    var user = await _userManager.GetUserAsync(info.Principal);
    if (user == null)
    {
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                OpenIddictConstants.Errors.InvalidGrant,
            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                "The refresh token is no longer valid."
        });

        return Forbid(properties, 
            OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    if (user.EmailConfirmed == false)
    {
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                OpenIddictConstants.Errors.InvalidGrant,
            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                "Email address not confirmed."
        });

        return Forbid(properties, 
            OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    if (!await _signInManager.CanSignInAsync(user))
    {
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                OpenIddictConstants.Errors.InvalidGrant,
            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                "The user is no longer allowed to sign in."
        });

        return Forbid(properties, 
            OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    foreach (var claim in info.Principal.Claims)
    {
        claim.SetDestinations(GetDestinations(claim, info.Principal));
    }

    return SignIn(info.Principal, 
        OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

private async Task<IActionResult> CheckPasswordGrantType(OpenIddictRequest request)
{
    var user = await _userManager.FindByNameAsync(request.Username);
    if (user == null)
    {
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            [OpenIddictServerAspNetCoreConstants.Properties.Error] = 
            OpenIddictConstants.Errors.InvalidGrant,
            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                "The username/password couple is invalid."
        });

        return Forbid(properties, 
            OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    if (user.EmailConfirmed == false)
    {
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            [OpenIddictServerAspNetCoreConstants.Properties.Error] = 
            OpenIddictConstants.Errors.AccessDenied,
            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = 
            "Email address not confirmed."
        });

        return Forbid(properties, 
            OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
    }

    var result = 
        await _signInManager.CheckPasswordSignInAsync(user, request.Password, true);
    if (!result.Succeeded)
    {
        if (result.IsLockedOut)
        {
            var properties =
                new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                    OpenIddictConstants.Errors.InvalidRequest,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                    "Account locked : too many attempts."
            });

            return Forbid(properties, 
                OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
        }
        else
        {
            var properties = 
                new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                    OpenIddictConstants.Errors.InvalidGrant,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                    "The username/password couple is invalid."
            });
            return Forbid(properties, 
                OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
        }
    }

    var principal = await _signInManager.CreateUserPrincipalAsync(user);
    principal.SetScopes(new[]
    {
                OpenIddictConstants.Scopes.OpenId,
                OpenIddictConstants.Scopes.Email,
                OpenIddictConstants.Scopes.Profile,
                OpenIddictConstants.Scopes.Roles,
                OpenIddictConstants.Scopes.OfflineAccess
            }.Intersect(request.GetScopes()));

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

    return SignIn(principal, 
    OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}

private IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal)
{
    switch (claim.Type)
    {
        case OpenIddictConstants.Claims.Name:
            yield return OpenIddictConstants.Destinations.AccessToken;

            if (principal.HasScope(OpenIddictConstants.Scopes.Profile))
                yield return OpenIddictConstants.Destinations.IdentityToken;

            yield break;

        case OpenIddictConstants.Claims.Email:
            yield return OpenIddictConstants.Destinations.AccessToken;

            if (principal.HasScope(OpenIddictConstants.Scopes.Email))
                yield return OpenIddictConstants.Destinations.IdentityToken;

            yield break;

        case OpenIddictConstants.Claims.Role:
            yield return OpenIddictConstants.Destinations.AccessToken;

            if (principal.HasScope(OpenIddictConstants.Scopes.Roles))
                yield return OpenIddictConstants.Destinations.IdentityToken;

            yield break;

        case "AspNet.Identity.SecurityStamp": yield break;

        default:
            yield return OpenIddictConstants.Destinations.AccessToken;
            yield break;
    }
}

Most of the code is coming from the samples from OpenIddict.

I really don't understand what's wrong

Try to add setCopes also to CheckRefreshTokenGrantType, I use it and it works for me:

if (request.IsRefreshTokenGrantType())
        {
            // Retrieve the claims principal stored in the refresh token.
            var info = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

            // Retrieve the user profile corresponding to the refresh token.
            // Note: if you want to automatically invalidate the 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)
            {
                var properties = new AuthenticationProperties(new Dictionary<string, string>
                {
                    [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                    [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The refresh token is no longer valid."
                });

                return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            // Ensure the user is still allowed to sign in.
            if (!await _signInManager.CanSignInAsync(user))
            {
                var properties = new AuthenticationProperties(new Dictionary<string, string>
                {
                    [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
                    [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
                });

                return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }

            // 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);

            //Add claims to the LocalTokenValidationApi 
            principal.SetResources("LocalTokenValidationApi");

            // Set the list of scopes granted to the client application.
            principal.SetScopes(new[]
            {
                Scopes.OpenId,
                Scopes.Email,
                Scopes.Profile,
                Scopes.Roles,
                Scopes.OfflineAccess
            }.Intersect(request.GetScopes()));

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

            return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM