This post is the sixth part of a series of blog posts entitled Creating your own OpenID Connect server with ASOS:
- Introduction
- Choosing the right flow(s)
- Registering the middleware in the ASP.NET Core pipeline
- Creating your own authorization provider
- Implementing the resource owner password credentials grant
- Implementing the authorization code and implicit flows
- Adding custom claims and granting scopes
- Testing your authorization server with Postman
- Conclusion
To support interactive flows like the authorization code or the implicit flows, the ValidateAuthorizationRequest
event must be implemented to validate the authorization request sent by the client application.
Implementing ValidateAuthorizationRequest
to validate response_type
, client_id
and redirect_uri
To support interactive flows, you must implement ValidateAuthorizationRequest
to validate the client_id
and the redirect_uri
parameters provided by the client application to ensure they correspond to a registered client.
Ideally, the response_type
parameter should also be validated to ensure that a client_id
corresponding to a confidential application cannot be used with the implicit/hybrid flow to prevent [downgrade attacks](http://homakov.blogspot.com/2012/08/saferweb-OAuth 2.0a-or-lets-just-fix-it.html).
In pure OAuth 2.0, redirect_uri
was not mandatory but is now required by the OpenID Connect specification. To support legacy clients, ASOS doesn't reject authorization requests missing the redirect_uri
parameter if the openid
scope is not present, but in this case, it's up to you to call context.Validate(...)
with the redirect_uri
the user agent should be redirected to. If you don't need to support such clients, consider rejecting the authorization requests that don't specify a redirect_uri
.
While the OpenID Connect specification explicitly states that the redirect_uri
MUST exactly match one of the callback URLs associated with the client application, you're actually free to implement a relaxed comparison policy to support advanced scenarios (e.g domain-only/subdomain comparison or wildcard support): use this ability with extreme caution to avoid introducing an open redirect vulnerability.
Nothing surprising: the exact implementation of ValidateAuthorizationRequest
will depend on the flows you want to support (e.g authorization code/implicit/hybrid) and on how you store your application details (e.g hardcoded or in a database).
1 | public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context) |
Implementing ValidateTokenRequest
to validate the grant type and the client application credentials
Similarly to the resource owner password credentials grant, the ValidateTokenRequest
event must be implemented when using the authorization code flow, as it relies on the token endpoint to get a new access token.
Since the same concerns apply here (including grant type and client authentication validation), don't hesitate to (re)read the
previous postif you're unsure how you're supposed to implement the ValidateTokenRequest
event.
For instance, here's what you'll typically do for a mobile application, for which client authentication cannot be enforced:
1 | public override Task ValidateTokenRequest(ValidateTokenRequestContext context) |
Implementing your own authorization/consent pages
As mentioned in the introduction post, ASOS doesn't come with a consent page and it's up to the implementer to provide one if necessary.
This can be done using the framework of your choice: ASP.NET Core MVC, Nancy or any other OWIN-compatible framework. Since MVC is by far the most popular framework, I'll only demonstrate how you can implement your own consent form using MVC controllers, but you can also find a sample using the OWIN/Katana version of ASOS with Nancy in the GitHub repository.
This is probably the most critical step when creating your own identity server as the consent page is an important attack vector: to prevent clickjacking/cursorjacking and cross-site request forgery attacks, you MUST implement appropriate countermeasures (e.g framekillers scripts, X-Frame-Options, Content-Security-Policy and antiforgery tokens).
In MVC, cross-site request forgery and clickjacking attacks are usually mitigated using the [ValidateAntiforgeryToken]
attribute, that uses the whole new Antiforgery stack under the hood.
Creating an Authorize
action to display the consent form
The Authorize
action represents the initial step of the authorization process: it's the first page the user will be redirected to by the client application and where he/she will be invited to accept or reject the authorization request.
In most cases, you'll likely want to ensure the user is logged in and registered before displaying a consent form, but merging the login form and the consent form is also possible: you're only limited by your imagination.
Of course, you're responsible for providing the required infrastructure needed to log your users in, which can be easily implemented using ASP.NET Core Identity and the AccountController
that comes with the default Visual Studio templates and supports both local and external authentication.
Here's a simple example using an Authorize
action and 2 Razor views:
1 | [ ] |
1 | public class AuthorizeViewModel |
1 | @model AuthorizeViewModel |
1 | @model ErrorViewModel |
Creating an Accept
action to let the user validate the consent form
When the user approves the authorization request, the only thing you have to have to do is create a ClaimsIdentity
containing the user claims and call ControllerBase.SignIn
to inform the OpenID Connect server middleware that a successful authorization response should be returned to the client application.
This step is very similar to how you implemented HandleTokenRequest
in the previous post:
1 | [ ] |
Creating a Deny
action to allow rejecting the authorization request
Rejecting an authorization request couldn't be simpler with ASOS: call Forbid(OpenIdConnectServerDefaults.AuthenticationScheme)
to return a ForbidResult
and ASOS will immediately redirect the user agent to the client application.
1 | [ ] |
Implementing MatchEndpoint
to dynamically determine the request type
By default, ASOS only handles the HTTP requests whose path exactly matches one of the pre-defined endpoints registered in the OpenID Connect server options.
You can override the default endpoint selection routine by implementing the MatchEndpoint
event, which can be particularly useful to extract authorization requests from subpaths like /connect/authorize/accept
and /connect/authorize/deny
, that would be ignored otherwise.
1 | public override Task MatchEndpoint(MatchEndpointContext context) |
Next part: Adding custom claims and granting scopes.