This post is the sixth part of a series of blog posts entitled Creating your own OpenID Connect server with ASOS:
- 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
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.
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.
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.
In pure OAuth2,
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
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).
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 post if you're unsure how you're supposed to implement the
For instance, here's what you'll typically do for a mobile application, for which client authentication cannot be enforced:
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.
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 of 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:
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:
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.
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/deny, that would be ignored otherwise.
Next part: Adding custom claims and granting scopes.