Browse Source

Implement POST authorization requests support using user sessions

pull/147/head
Kévin Chalet 10 years ago
parent
commit
34fa0c1eb5
  1. 2
      samples/Mvc.Client/Startup.cs
  2. 4
      samples/Mvc.Server/Startup.cs
  3. 1
      samples/Mvc.Server/project.json
  4. 94
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  5. 4
      src/OpenIddict.Core/OpenIddictConstants.cs
  6. 22
      src/OpenIddict.Mvc/OpenIddictController.cs

2
samples/Mvc.Client/Startup.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@ -40,6 +41,7 @@ namespace Mvc.Client {
// Use the authorization code flow.
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
// Note: setting the Authority allows the OIDC client middleware to automatically
// retrieve the identity provider's configuration and spare you from setting

4
samples/Mvc.Server/Startup.cs

@ -22,6 +22,8 @@ namespace Mvc.Server {
services.AddMvc();
services.AddSession();
services.AddEntityFramework()
.AddEntityFrameworkSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
@ -120,6 +122,8 @@ namespace Mvc.Server {
app.UseSession();
app.UseSession();
app.UseOpenIddict();
app.UseMvcWithDefaultRoute();

1
samples/Mvc.Server/project.json

@ -27,6 +27,7 @@
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Session": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0",

94
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs

@ -13,14 +13,18 @@ using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TUser : class where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override Task ExtractAuthorizationRequest([NotNull] ExtractAuthorizationRequestContext context) {
public override async Task ExtractAuthorizationRequest([NotNull] ExtractAuthorizationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Reject requests using the unsupported request parameter.
@ -32,7 +36,7 @@ namespace OpenIddict.Infrastructure {
error: OpenIdConnectConstants.Errors.RequestNotSupported,
description: "The request parameter is not supported.");
return Task.FromResult(0);
return;
}
// Reject requests using the unsupported request_uri parameter.
@ -44,10 +48,43 @@ namespace OpenIddict.Infrastructure {
error: OpenIdConnectConstants.Errors.RequestUriNotSupported,
description: "The request_uri parameter is not supported.");
return Task.FromResult(0);
return;
}
return Task.FromResult(0);
// If a request_id parameter can be found in the authorization request,
// restore the complete authorization request stored in the user session.
if (!string.IsNullOrEmpty(context.Request.GetRequestId())) {
// Ensure session support has been enabled for this request.
if (context.HttpContext.Features.Get<ISessionFeature>() == null) {
services.Logger.LogError("The authorization request was rejected because the session middleware " +
"was not correctly registered. Session support must be enabled to allow " +
"processing authorization requests specifying a request_id parameter.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The request_id parameter is not allowed by this authorization server.");
return;
}
// Load the session from the session store.
await context.HttpContext.Session.LoadAsync();
var payload = context.HttpContext.Session.Get(OpenIddictConstants.Environment.Request + context.Request.GetRequestId());
if (payload == null) {
services.Logger.LogError("The authorization request was rejected because an unknown " +
"or invalid request_id parameter was specified.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Invalid request: timeout expired.");
return;
}
// Restore the authorization request parameters from the serialized payload.
context.Request.Import(payload);
}
}
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) {
@ -279,7 +316,56 @@ namespace OpenIddict.Infrastructure {
return;
}
if (context.HttpContext.Request.Path == context.Options.AuthorizationEndpointPath &&
string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) {
// Ensure session support has been enabled for this request.
if (context.HttpContext.Features.Get<ISessionFeature>() == null) {
services.Logger.LogError("The authorization request was rejected because the session middleware " +
"was not correctly registered. Session support must be enabled to allow " +
"processing POST authorization requests.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "POST authorization requests are not allowed by this authorization server.");
return;
}
// Generate a request identifier. Note: using a crypto-secure
// random number generator is not necessary in this case.
var identifier = Guid.NewGuid().ToString();
// Load the session from the session store.
await context.HttpContext.Session.LoadAsync();
// Store the serialized authorization request parameters in the user session.
context.HttpContext.Session.Set(OpenIddictConstants.Environment.Request + identifier, context.Request.Export());
// Add the request_id to the current URL.
var address = QueryHelpers.AddQueryString(
uri: context.HttpContext.Request.GetEncodedUrl(),
name: OpenIdConnectConstants.Parameters.RequestId, value: identifier);
context.HttpContext.Response.Redirect(address);
context.HandleResponse();
return;
}
context.SkipToNextMiddleware();
}
public override Task ApplyAuthorizationResponse([NotNull] ApplyAuthorizationResponseContext context) {
// Note: the ApplyAuthorizationResponse event is called for both successful
// and errored authorization responses but discrimination is not necessary here,
// as the authorization request must be removed from the user session in both cases.
// Remove the authorization request from the user session.
if (!string.IsNullOrEmpty(context.Request.GetRequestId())) {
context.HttpContext.Session.Remove(OpenIddictConstants.Environment.Request + context.Request.GetRequestId());
}
return Task.FromResult(0);
}
}
}

4
src/OpenIddict.Core/OpenIddictConstants.cs

@ -15,6 +15,10 @@ namespace OpenIddict {
public const string Public = "public";
}
public static class Environment {
public const string Request = "openiddict-request:";
}
public static class Scopes {
public const string Roles = "roles";
}

22
src/OpenIddict.Mvc/OpenIddictController.cs

@ -5,8 +5,6 @@
*/
using System;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
@ -23,7 +21,7 @@ namespace OpenIddict.Mvc {
// Note: this controller is generic and doesn't need to be marked as internal to prevent MVC from discovering it.
public class OpenIddictController<TUser, TApplication, TAuthorization, TToken> : Controller
where TUser : class where TApplication : class where TAuthorization : class where TToken : class {
[HttpGet, HttpPost]
[Authorize, HttpGet, HttpPost]
public virtual async Task<IActionResult> Authorize([FromServices] OpenIddictApplicationManager<TApplication> applications) {
// Note: when a fatal error occurs during the request processing, an OpenID Connect response
// is prematurely forged and added to the ASP.NET Core context by OpenIdConnectServerHandler.
@ -41,19 +39,6 @@ namespace OpenIddict.Mvc {
});
}
// Note: authentication could be theorically enforced at the filter level via AuthorizeAttribute
// but this authorization endpoint accepts both GET and POST requests while the cookie middleware
// only uses 302 responses to redirect the user agent to the login page, making it incompatible with POST.
// To work around this limitation, the OpenID Connect request is automatically saved in the cache and will be
// restored by the OpenID Connect server middleware after the external authentication process has been completed.
if (!User.Identities.Any(identity => identity.IsAuthenticated)) {
return Challenge(new AuthenticationProperties {
RedirectUri = Url.Action(nameof(Authorize), new {
request_id = request.GetRequestId()
})
});
}
// Note: the OpenID Connect server middleware automatically ensures an application
// corresponds to the client_id specified in the authorization request using
// IOpenIdConnectServerProvider.ValidateAuthorizationRequest (see OpenIddictProvider.cs).
@ -98,7 +83,10 @@ namespace OpenIddict.Mvc {
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = await users.CreateIdentityAsync(user, request.GetScopes());
Debug.Assert(identity != null);
if (identity == null) {
throw new InvalidOperationException("The authorization request was aborted because the user manager returned " +
$"a null identity for user '{await users.GetUserNameAsync(user)}'.");
}
var application = await applications.FindByClientIdAsync(request.ClientId);
if (application == null) {

Loading…
Cancel
Save