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.