Browse Source

Update OpenIddictProvider to make refresh token requests pass-through

pull/284/head
Kévin Chalet 9 years ago
parent
commit
39fd5763a9
  1. 62
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  2. 10
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  3. 29
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs
  4. 3
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs

62
samples/Mvc.Server/Controllers/AuthorizationController.cs

@ -35,6 +35,7 @@ namespace Mvc.Server {
_userManager = userManager; _userManager = userManager;
} }
#region Authorization code, implicit and implicit flows
// Note: to support interactive flows like the code flow, // Note: to support interactive flows like the code flow,
// you must provide your own authorization endpoint action: // you must provide your own authorization endpoint action:
@ -108,7 +109,9 @@ namespace Mvc.Server {
// to the post_logout_redirect_uri specified by the client application. // to the post_logout_redirect_uri specified by the client application.
return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme); return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme);
} }
#endregion
#region Password and refresh token flows
// Note: to support non-interactive flows like password, // Note: to support non-interactive flows like password,
// you must provide your own token endpoint action: // you must provide your own token endpoint action:
@ -169,13 +172,45 @@ namespace Mvc.Server {
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
} }
else if (request.IsRefreshTokenGrantType()) {
// Retrieve the claims principal stored in the refresh token.
var info = await HttpContext.Authentication.GetAuthenticateInfoAsync(
OpenIdConnectServerDefaults.AuthenticationScheme);
// Retrieve the user profile corresponding to the refresh token.
var user = await _userManager.GetUserAsync(info.Principal);
if (user == null) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The refresh token is no longer valid."
});
}
// Ensure the user is still allowed to sign in.
if (!await _signInManager.CanSignInAsync(user)) {
return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The user is no longer allowed to sign in."
});
}
// Create a new authentication ticket, but reuse the properties stored
// in the refresh token, including the scopes originally granted.
var ticket = await CreateTicketAsync(request, user, info.Properties);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse { return BadRequest(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported." ErrorDescription = "The specified grant type is not supported."
}); });
} }
#endregion
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) { private async Task<AuthenticationTicket> CreateTicketAsync(
OpenIdConnectRequest request, ApplicationUser user,
AuthenticationProperties properties = null) {
// Create a new ClaimsPrincipal containing the claims that // Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code. // will be used to create an id_token, a token or a code.
var principal = await _signInManager.CreateUserPrincipalAsync(user); var principal = await _signInManager.CreateUserPrincipalAsync(user);
@ -193,20 +228,21 @@ namespace Mvc.Server {
} }
// Create a new authentication ticket holding the user identity. // Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket( var ticket = new AuthenticationTicket(principal, properties,
principal, new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme); OpenIdConnectServerDefaults.AuthenticationScheme);
// Set the list of scopes granted to the client application. if (!request.IsRefreshTokenGrantType()) {
// Note: the offline_access scope must be granted // Set the list of scopes granted to the client application.
// to allow OpenIddict to return a refresh token. // Note: the offline_access scope must be granted
ticket.SetScopes(new[] { // to allow OpenIddict to return a refresh token.
OpenIdConnectConstants.Scopes.OpenId, ticket.SetScopes(new[] {
OpenIdConnectConstants.Scopes.Email, OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Profile, OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.OfflineAccess, OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles OpenIdConnectConstants.Scopes.OfflineAccess,
}.Intersect(request.GetScopes())); OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes()));
}
return ticket; return ticket;
} }

10
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs

@ -180,9 +180,6 @@ namespace OpenIddict.Infrastructure {
public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) { public override async Task HandleTokenRequest([NotNull] HandleTokenRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>(); var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
// Note: the OpenID Connect server middleware automatically reuses the authentication ticket
// stored in the authorization code to create a new identity. To ensure the user was not removed
// after the authorization code was issued, a new check is made before validating the request.
if (context.Request.IsAuthorizationCodeGrantType()) { if (context.Request.IsAuthorizationCodeGrantType()) {
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
@ -211,9 +208,6 @@ namespace OpenIddict.Infrastructure {
return; return;
} }
// Note: the OpenID Connect server middleware automatically reuses the authentication ticket
// stored in the refresh token to create a new identity. To ensure the user was not removed
// after the refresh token was issued, a new check is made before validating the request.
else if (context.Request.IsRefreshTokenGrantType()) { else if (context.Request.IsRefreshTokenGrantType()) {
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null."); Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
@ -240,10 +234,6 @@ namespace OpenIddict.Infrastructure {
if (context.Options.UseSlidingExpiration) { if (context.Options.UseSlidingExpiration) {
await services.Tokens.RevokeAsync(token); await services.Tokens.RevokeAsync(token);
} }
context.Validate(context.Ticket);
return;
} }
// Invoke the rest of the pipeline to allow // Invoke the rest of the pipeline to allow

29
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs

@ -516,9 +516,33 @@ namespace OpenIddict.Core.Tests.Infrastructure {
[Theory] [Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)] [InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)] [InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
[InlineData("urn:ietf:params:oauth:grant-type:custom_grant")] [InlineData("urn:ietf:params:oauth:grant-type:custom_grant")]
public async Task HandleTokenRequest_RequestsAreNotHandledLocally(string flow) { public async Task HandleTokenRequest_RequestsAreNotHandledLocally(string flow) {
// Arrange // Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = Mock.Of<object>();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => { var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => { builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>(); var application = Mock.Of<object>();
@ -534,6 +558,10 @@ namespace OpenIddict.Core.Tests.Infrastructure {
})); }));
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant"); builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
}); });
var client = new OpenIdConnectClient(server.CreateClient()); var client = new OpenIdConnectClient(server.CreateClient());
@ -543,6 +571,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
ClientId = "Fabrikam", ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = flow, GrantType = flow,
RefreshToken = "8xLOxBtZp8",
Username = "johndoe", Username = "johndoe",
Password = "A3ddj3w" Password = "A3ddj3w"
}); });

3
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs

@ -94,8 +94,7 @@ namespace OpenIddict.Core.Tests.Infrastructure {
app.UseOpenIddict(); app.UseOpenIddict();
app.Run(context => { app.Run(context => {
if (context.Request.Path == AuthorizationEndpoint || if (context.Request.Path == AuthorizationEndpoint || context.Request.Path == TokenEndpoint) {
context.Request.Path == TokenEndpoint) {
var request = context.GetOpenIdConnectRequest(); var request = context.GetOpenIdConnectRequest();
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme); var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);

Loading…
Cancel
Save