Browse Source

Remove OpenIddictMiddleware and the parameterless OpenIddictBuilder.EnableAuthorizationEndpoint() overloads

pull/203/head
Kévin Chalet 10 years ago
parent
commit
94f1189592
  1. 64
      README.md
  2. 54
      samples/Mvc.Server/Controllers/AuthorizationController.cs
  3. 180
      src/OpenIddict.Core/Infrastructure/OpenIddictMiddleware.cs
  4. 8
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  5. 60
      src/OpenIddict.Core/OpenIddictBuilder.cs
  6. 44
      src/OpenIddict.Core/OpenIddictDefaults.cs
  7. 24
      src/OpenIddict.Core/OpenIddictExtensions.cs

64
README.md

@ -150,7 +150,61 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
services.AddOpenIddict<ApplicationUser, IdentityRole<int>, ApplicationDbContext, int>()
```
## Enabling interactive flows support
- **Create your own authorization controller**:
To **support the password or the client credentials flow, you must provide your own token endpoint action**:
```csharp
[HttpPost("~/connect/token")]
public async Task<IActionResult> Exchange() {
var request = HttpContext.GetOpenIdConnectRequest();
if (request.IsPasswordGrantType()) {
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null) {
return Json(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant
});
}
// Ensure the password is valid.
if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
if (_userManager.SupportsUserLockout) {
await _userManager.AccessFailedAsync(user);
}
return Json(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.InvalidGrant
});
}
if (_userManager.SupportsUserLockout) {
await _userManager.ResetAccessFailedCountAsync(user);
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return Json(new OpenIdConnectResponse {
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType
});
}
```
To **enable authorization code/implicit flows support, you'll similarly have to create your own authorization endpoint action** and your own views/view models. The Mvc.Server sample comes with an [`AuthorizationController` that you can easily reuse in your application](https://github.com/openiddict/openiddict-core/blob/dev/samples/Mvc.Server/Controllers/AuthorizationController.cs).
![](https://cloud.githubusercontent.com/assets/6998306/10988233/d9026712-843a-11e5-8ff0-e7addffd727b.png)
- **Enable the corresponding flows in the OpenIddict options**:
@ -175,14 +229,6 @@ public void ConfigureServices(IServiceCollection services) {
}
```
- **Create your own authorization controller and your own views**:
**By default, OpenIddict processes authorization requests without requiring user consent**, which allows you to use OpenIddict with your own SPA apps without having to add custom code.
**To display a confirmation form, you must create your own controller** and your own views/view models. The Mvc.Server sample comes with an [`AuthorizationController` that you can easily reuse in your application](https://github.com/openiddict/openiddict-core/blob/dev/samples/Mvc.Server/Controllers/AuthorizationController.cs).
![](https://cloud.githubusercontent.com/assets/6998306/10988233/d9026712-843a-11e5-8ff0-e7addffd727b.png)
- **Register your client application**:
```csharp

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

@ -36,9 +36,6 @@ namespace Mvc.Server {
_userManager = userManager;
}
// Note: if you don't provide your own authorization action, OpenIddict will
// directly process authorization requests without requiring user consent.
[Authorize, HttpGet, Route("~/connect/authorize")]
public async Task<IActionResult> Authorize() {
// Extract the authorization request from the ASP.NET environment.
@ -100,9 +97,6 @@ namespace Mvc.Server {
return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
}
// Note: if you don't provide your own logout action, OpenIddict will
// directly process logout requests without requiring user confirmation.
[HttpGet("~/connect/logout")]
public IActionResult Logout() {
// Extract the authorization request from the ASP.NET environment.
@ -126,5 +120,53 @@ namespace Mvc.Server {
// to the post_logout_redirect_uri specified by the client application.
return SignOut(OpenIdConnectServerDefaults.AuthenticationScheme);
}
// Note: to support the password grant type, you must provide your own token endpoint action:
// [HttpPost("~/connect/token")]
// public async Task<IActionResult> Exchange() {
// var request = HttpContext.GetOpenIdConnectRequest();
//
// if (request.IsPasswordGrantType()) {
// var user = await _userManager.FindByNameAsync(request.Username);
// if (user == null) {
// return Json(new OpenIdConnectResponse {
// Error = OpenIdConnectConstants.Errors.InvalidGrant
// });
// }
//
// // Ensure the password is valid.
// if (!await _userManager.CheckPasswordAsync(user, request.Password)) {
// if (_userManager.SupportsUserLockout) {
// await _userManager.AccessFailedAsync(user);
// }
//
// return Json(new OpenIdConnectResponse {
// Error = OpenIdConnectConstants.Errors.InvalidGrant
// });
// }
//
// if (_userManager.SupportsUserLockout) {
// await _userManager.ResetAccessFailedCountAsync(user);
// }
//
// var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
//
// // Create a new authentication ticket holding the user identity.
// var ticket = new AuthenticationTicket(
// new ClaimsPrincipal(identity),
// new AuthenticationProperties(),
// OpenIdConnectServerDefaults.AuthenticationScheme);
//
// ticket.SetResources(request.GetResources());
// ticket.SetScopes(request.GetScopes());
//
// return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
// }
//
// return Json(new OpenIdConnectResponse {
// Error = OpenIdConnectConstants.Errors.UnsupportedGrantType
// });
// }
}
}

180
src/OpenIddict.Core/Infrastructure/OpenIddictMiddleware.cs

@ -1,180 +0,0 @@
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public class OpenIddictMiddleware<TUser, TApplication, TAuthorization, TScope, TToken>
where TUser : class where TApplication : class
where TAuthorization : class where TScope : class where TToken : class {
private readonly RequestDelegate next;
public OpenIddictMiddleware([NotNull] RequestDelegate next) {
this.next = next;
}
public async Task Invoke([NotNull] HttpContext context) {
// Invoke the rest of the pipeline to allow handling
// authorization, logout or token requests in user code.
await next(context);
// If the request was already handled, skip the default logic.
if (context.Response.HasStarted || context.Response.StatusCode != 404) {
return;
}
// If the request doesn't correspond to an OpenID Connect request, ignore it.
var request = context.GetOpenIdConnectRequest();
if (request == null || (!request.IsAuthorizationRequest() &&
!request.IsLogoutRequest() &&
!request.IsTokenRequest())) {
return;
}
// If an OpenID Connect response was already prepared, bypass the default logic.
var response = context.GetOpenIdConnectResponse();
if (response != null) {
return;
}
// Resolve the OpenIddict services from the scoped container.
var services = context.RequestServices.GetRequiredService<OpenIddictServices<
TUser, TApplication, TAuthorization, TScope, TToken>>();
// Reset the response status code to allow the OpenID Connect server
// middleware to apply a challenge, signin or logout response.
context.Response.StatusCode = 200;
ClaimsPrincipal principal = null;
if (request.IsAuthorizationRequest()) {
// If the user is not logged in, return a challenge response.
if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) {
await context.Authentication.ChallengeAsync();
return;
}
// Retrieve the profile of the logged in user. If the user
// cannot be found, return a challenge response.
var user = await services.Users.GetUserAsync(context.User);
if (user == null) {
await context.Authentication.ChallengeAsync();
return;
}
services.Logger.LogInformation("The authorization request was handled without asking for user consent.");
principal = new ClaimsPrincipal(await services.Users.CreateIdentityAsync(user, request.GetScopes()));
}
else if (request.IsLogoutRequest()) {
// 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 services.SignIn.SignOutAsync();
await context.Authentication.SignOutAsync(services.Options.AuthenticationScheme);
services.Logger.LogInformation("The logout request was handled without asking for user consent.");
return;
}
else if (request.IsTokenRequest()) {
Debug.Assert(request.IsClientCredentialsGrantType() || request.IsPasswordGrantType(),
"Only grant_type=client_credentials and grant_type=password requests should be handled here.");
services.Logger.LogInformation("The token request was automatically handled.");
if (request.IsClientCredentialsGrantType()) {
// Retrieve the application details corresponding to the requested client_id.
// Note: this call shouldn't return a null instance, but a race condition may occur
// if the application was removed after the initial check made by ValidateTokenRequest.
var application = await services.Applications.FindByClientIdAsync(request.ClientId);
if (application == null) {
services.Logger.LogError("The token request was aborted because the client application " +
"was not found in the database: '{ClientId}'.", request.ClientId);
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme);
return;
}
var identity = new ClaimsIdentity(services.Options.AuthenticationScheme);
// Note: the name identifier is always included in both identity and
// access tokens, even if an explicit destination is not specified.
identity.AddClaim(ClaimTypes.NameIdentifier, request.ClientId);
identity.AddClaim(ClaimTypes.Name, await services.Applications.GetDisplayNameAsync(application),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
principal = new ClaimsPrincipal(identity);
}
else if (request.IsPasswordGrantType()) {
// Retrieve the user profile corresponding to the specified username.
var user = await services.Users.FindByNameAsync(request.Username);
if (user == null) {
services.Logger.LogError("The token request was rejected because no user profile corresponding to " +
"the specified username was found: '{Username}'.", request.Username);
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme);
return;
}
// Ensure the username/password couple is valid.
if (!await services.Users.CheckPasswordAsync(user, request.Password)) {
services.Logger.LogError("The token request was rejected because the password didn't match " +
"the password associated with the account '{Username}'.", request.Username);
if (services.Users.SupportsUserLockout) {
await services.Users.AccessFailedAsync(user);
if (await services.Users.IsLockedOutAsync(user)) {
services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", request.Username);
}
}
await context.Authentication.ForbidAsync(services.Options.AuthenticationScheme);
return;
}
if (services.Users.SupportsUserLockout) {
await services.Users.ResetAccessFailedCountAsync(user);
}
principal = new ClaimsPrincipal(await services.Users.CreateIdentityAsync(user, request.GetScopes()));
}
}
// At this stage, don't alter the response
// if a sign-in operation can't be performed.
if (principal != null) {
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
principal, new AuthenticationProperties(),
services.Options.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
await context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
}
}
}
}

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

@ -401,10 +401,6 @@ namespace OpenIddict.Infrastructure {
}
}
// Call context.SkipToNextMiddleware() to invoke the next middleware in the pipeline.
// This allows handling grant_type=password requests in a custom controller action.
// If the request is not handled in user code, OpenIddictMiddleware will automatically
// create and return a token response using the default authentication logic.
context.SkipToNextMiddleware();
}
@ -414,10 +410,6 @@ namespace OpenIddict.Infrastructure {
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId) &&
!string.IsNullOrEmpty(context.Request.ClientSecret), "The client credentials shouldn't be null.");
// Call context.SkipToNextMiddleware() to invoke the next middleware in the pipeline.
// This allows handling grant_type=client_credentials requests in a custom controller action.
// If the request is not handled in user code, OpenIddictMiddleware will automatically
// create and return a token response using the default authentication logic.
context.SkipToNextMiddleware();
}
}

60
src/OpenIddict.Core/OpenIddictBuilder.cs

@ -506,15 +506,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the authorization endpoint using the default endpoint path (/connect/authorize).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableAuthorizationEndpoint() {
return EnableAuthorizationEndpoint(OpenIddictDefaults.AuthorizationEndpointPath);
}
/// <summary>
/// Enables the authorization endpoint using the specified path.
/// Enables the authorization endpoint.
/// </summary>
/// <param name="path">The relative path of the authorization endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
@ -523,15 +515,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the introspection endpoint using the default endpoint path (/connect/introspect).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableIntrospectionEndpoint() {
return EnableIntrospectionEndpoint(OpenIddictDefaults.IntrospectionEndpointPath);
}
/// <summary>
/// Enables the introspection endpoint using the specified path.
/// Enables the introspection endpoint.
/// </summary>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
@ -540,15 +524,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the logout endpoint using the default endpoint path (/connect/logout).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableLogoutEndpoint() {
return EnableLogoutEndpoint(OpenIddictDefaults.LogoutEndpointPath);
}
/// <summary>
/// Enables the logout endpoint using the specified path.
/// Enables the logout endpoint.
/// </summary>
/// <param name="path">The relative path of the logout endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
@ -557,15 +533,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the revocation endpoint using the default endpoint path (/connect/revoke).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableRevocationEndpoint() {
return EnableRevocationEndpoint(OpenIddictDefaults.RevocationEndpointPath);
}
/// <summary>
/// Enables the revocation endpoint using the specified path.
/// Enables the revocation endpoint.
/// </summary>
/// <param name="path">The relative path of the revocation endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
@ -574,15 +542,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the token endpoint using the default endpoint path (/connect/token).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableTokenEndpoint() {
return EnableTokenEndpoint(OpenIddictDefaults.TokenEndpointPath);
}
/// <summary>
/// Enables the token endpoint using the specified path.
/// Enables the token endpoint.
/// </summary>
/// <param name="path">The relative path of the token endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
@ -591,15 +551,7 @@ namespace Microsoft.AspNetCore.Builder {
}
/// <summary>
/// Enables the userinfo endpoint using the default endpoint path (/connect/userinfo).
/// </summary>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public virtual OpenIddictBuilder EnableUserinfoEndpoint() {
return EnableUserinfoEndpoint(OpenIddictDefaults.UserinfoEndpointPath);
}
/// <summary>
/// Enables the userinfo endpoint using the specified path.
/// Enables the userinfo endpoint.
/// </summary>
/// <param name="path">The relative path of the userinfo endpoint.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>

44
src/OpenIddict.Core/OpenIddictDefaults.cs

@ -1,44 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using AspNet.Security.OpenIdConnect.Server;
namespace OpenIddict {
/// <summary>
/// Exposes the default values used by OpenIddict.
/// </summary>
public static class OpenIddictDefaults {
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.AuthorizationEndpointPath"/>.
/// </summary>
public const string AuthorizationEndpointPath = "/connect/authorize";
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.IntrospectionEndpointPath"/>.
/// </summary>
public const string IntrospectionEndpointPath = "/connect/introspect";
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.LogoutEndpointPath"/>.
/// </summary>
public const string LogoutEndpointPath = "/connect/logout";
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.RevocationEndpointPath"/>.
/// </summary>
public const string RevocationEndpointPath = "/connect/revoke";
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.TokenEndpointPath"/>.
/// </summary>
public const string TokenEndpointPath = "/connect/token";
/// <summary>
/// Default value for <see cref="OpenIdConnectServerOptions.UserinfoEndpointPath"/>.
/// </summary>
public const string UserinfoEndpointPath = "/connect/userinfo";
}
}

24
src/OpenIddict.Core/OpenIddictExtensions.cs

@ -7,7 +7,6 @@
using System;
using System.Linq;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
@ -64,16 +63,6 @@ namespace Microsoft.AspNetCore.Builder {
builder.Configure(options => {
// Register the OpenID Connect server provider in the OpenIddict options.
options.Provider = new OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken>();
// Register the OpenID Connect server middleware as a native module.
options.Modules.Add(new OpenIddictModule("OpenID Connect server", 0, app => {
app.UseMiddleware<OpenIdConnectServerMiddleware>();
}));
// Register the OpenIddict middleware as a built-in module.
options.Modules.Add(new OpenIddictModule("OpenIddict", 1, app => {
app.UseMiddleware<OpenIddictMiddleware<TUser, TApplication, TAuthorization, TScope, TToken>>();
}));
});
// Register the OpenIddict core services in the DI container.
@ -84,12 +73,6 @@ namespace Microsoft.AspNetCore.Builder {
builder.Services.TryAddScoped<OpenIddictUserManager<TUser>>();
builder.Services.TryAddScoped<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Override the default options manager for IOptions<OpenIdConnectServerOptions>
// to ensure the OpenID Connect server middleware uses the OpenIddict options.
builder.Services.TryAddScoped<IOptions<OpenIdConnectServerOptions>>(provider => {
return provider.GetRequiredService<IOptions<OpenIddictOptions>>();
});
return builder;
}
@ -149,8 +132,13 @@ namespace Microsoft.AspNetCore.Builder {
"client credentials, password and refresh token flows.");
}
// Get the modules registered by the application
// and add the OpenID Connect server middleware.
var modules = options.Modules.ToList();
modules.Add(new OpenIddictModule("OpenID Connect server", 0, builder => builder.UseOpenIdConnectServer(options)));
// Register the OpenIddict modules in the ASP.NET Core pipeline.
foreach (var module in options.Modules.OrderBy(module => module.Position)) {
foreach (var module in modules.OrderBy(module => module.Position)) {
if (module?.Registration == null) {
throw new InvalidOperationException("An invalid OpenIddict module was registered.");
}

Loading…
Cancel
Save