Creating your own OpenID Connect server with ASOS: implementing the authorization code and implicit flows

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).

Read more

Creating your own OpenID Connect server with ASOS: implementing the resource owner password credentials grant

Implementing the resource owner password credentials grant (abbreviated ROPC for brevity) is quite easy with ASOS as the only thing you have to do is to provide your own implementation of ValidateTokenRequest and HandleTokenRequest.

But to properly implement these events, you first need to determine what's the best client authentication policy for your application.


Implementing ValidateTokenRequest to validate the grant type and the client application credentials

When implementing flows using backchannel communication (i.e resource owner password credentials grant, client credentials grant, authorization code flow or refresh token grant), the ValidateTokenRequest event must be overridden to validate the token request.

So, what are you supposed to validate in this event? Mainly two things:

  • The grant type: in most cases, you'll likely want to restrict the grants a client application is allowed to use (e.g resource owner password credentials only): ValidateTokenRequest is the best place for that.

It should be noted that ASOS doesn't validate the grant_type value, that can even contain a custom value for extension grants: if you only want to support standard grants, it's up to you to reject the token request by calling context.Reject().

IsAuthorizationCodeGrantType(), IsRefreshTokenGrantType(), IsPasswordGrantType() and IsClientCredentialsGrantType() can be used for this exact purpose.

  • The client credentials (client_id/client_secret): the OAuth 2.0 specification explicitly states that confidential applications (i.e applications that are able to keep their credentials secret, like server-side apps) must authenticate when using the token endpoint. This security measure is extremely important as it's the only way to prevent malicious applications from retrieving an access token on behalf of a legitimate confidential application.

Contrary to popular belief, client authentication is never mandatory when using the token endpoint (except for the client credentials grant), which means that public applications like JS or mobile apps are allowed to use the resource owner password grant without having to send their credentials.

In practice, it's up to you to decide whether your token endpoint should accept unauthenticated requests or not, depending on the type of client you'll use.

Read more

Creating your own OpenID Connect server with ASOS: creating your own authorization provider

ASOS leverages the same events model as the rest of the ASP.NET Core security stack: often hard to understand for beginners, this pattern (inherited from OWIN/Katana) proved to be extremely powerful by offering full flexibility on the request processing.

To help make things clearer before trying to implement a concrete flow, here's a quick overview of how it works with ASOS:


OpenIdConnectServerProvider and the events model

OpenIdConnectServerProvider is ASOS' main extensibility hook: its methods (named events or notifications) are invoked by OpenIdConnectServerHandler for every OpenID Connect request to give you a chance to control how the request is handled. Depending on the flows you want to support, you'll need to implement different events.

You have 2 options to create your own provider:

  • Directly instantiante an OpenIdConnectServerProvider and use inline delegates. This approach is perfect when implementing a simple server that mainly relies on hardcoded values.
1
2
3
4
5
6
7
8
9
10
11
12
13
app.UseOpenIdConnectServer(options =>
{
options.Provider = new OpenIdConnectServerProvider
{
// Implement OnValidateAuthorizationRequest to
// support interactive flows (code/implicit/hybrid).
OnValidateAuthorizationRequest = async context => { ... },

// Implement OnValidateTokenRequest to support flows using the token endpoint
// (code/refresh token/password/client credentials/custom grant).
OnValidateTokenRequest = async context => { ... }
};
});

You can also directly set the events properties without having to manually instantiate a OpenIdConnectServerProvider, as ASOS always registers a default OpenIdConnectServerProvider instance for you:

1
2
3
4
5
6
7
8
9
10
app.UseOpenIdConnectServer(options =>
{
// Implement OnValidateAuthorizationRequest to
// support interactive flows (code/implicit/hybrid).
options.Provider.OnValidateAuthorizationRequest = async context => { ... };

// Implement OnValidateTokenRequest to support flows using the token endpoint
// (code/refresh token/password/client credentials/custom grant).
options.Provider.OnValidateTokenRequest = async context => { ... };
});
  • Create your own subclass of OpenIdConnectServerProvider and override the virtual methods you want to implement. This is clearly the best approach when implementing a more complex authorization server.
1
2
3
4
5
6
7
8
9
public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
// Implement OnValidateAuthorizationRequest to support interactive flows (code/implicit/hybrid).
public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context) { ... }

// Implement OnValidateTokenRequest to support flows using the token endpoint
// (code/refresh token/password/client credentials/custom grant).
public override async Task ValidateTokenRequest(ValidateTokenRequestContext context) { ... }
}
1
2
3
4
app.UseOpenIdConnectServer(options =>
{
options.Provider = new AuthorizationProvider();
});

It's important to note that the authorization provider is always a singleton: don't try to inject scoped dependencies in its constructor. To resolve scoped dependencies (e.g an Entity Framework DbContext), use the context.HttpContext.RequestServices property to access the scoped container.

You can read this thread for more information about this limitation/design choice, which is not specific to ASOS and impacts all the security middleware sharing the same events model. It might be fixed in a future version, though.

Read more

Creating your own OpenID Connect server with ASOS: registering the middleware in the ASP.NET Core pipeline

In the previous post (Choosing the right flow(s)), we saw the differences between the various OAuth 2.0/OpenID Connect flows. In this one, we'll see how to reference the ASOS package and how to register it in the ASP.NET Core pipeline.


Referencing the OpenID Connect server middleware package

This post was updated to reflect the latest changes introduced in the 1.0.0 RTM version of ASOS.

To reference ASOS, simply add "AspNet.Security.OpenIdConnect.Server": "1.0.0" under the dependencies node of your .csproj file:

1
2
3
4
5
6
7
<Project Sdk="Microsoft.NET.Sdk.Web">

<ItemGroup>
<PackageReference Include="AspNet.Security.OpenIdConnect.Server" Version="1.0.0" />
</ItemGroup>

</Project>

Referencing the OAuth 2.0 validation middleware

You'll also need to add the validation middleware, in charge of verifying/decrypting the tokens produced by ASOS and protecting your API endpoints:

1
2
3
4
5
6
7
<Project Sdk="Microsoft.NET.Sdk.Web">

<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.Validation" Version="1.0.0" />
</ItemGroup>

</Project>

The validation middleware is similar to the JWT bearer middleware developed by the ASP.NET team but was specifically designed to use the encrypted tokens issued by ASOS and to offer a much easier experience (it doesn't require any explicit configuration by default).

Starting with ASOS beta5, JWT is no longer the default format for access tokens, but is still supported natively. To use JWT tokens instead of opaque/encrypted tokens, follow the steps below:

  • Remove the reference to AspNet.Security.OAuth.Validation in project.json.
  • Remove the validation middleware registration (app.UseOAuthValidation()),
  • Reference the Microsoft.AspNetCore.Authentication.JwtBearer package.
  • Register the JWT middleware using app.UseJwtBearerAuthentication().
  • Assign options.AccessTokenHandler = new JwtSecurityTokenHandler() to override the default format.

Read more

Creating your own OpenID Connect server with ASOS: choosing the right flow(s)

ASOS offers built-in support for all the standard flows defined by the OAuth 2.0 and OpenID Connect core specifications: the authorization code flow, the implicit flow, the hybrid flow (which is basically a mix between the first two flows), the resource owner password credentials grant and the client credentials grant.

Though not specific to ASOS, choosing the best flow(s) for your application is an important prerequisite when implementing your own authorization server ; so here's a quick overview of the different OAuth 2.0/OpenID Connect flows:


Non-interactive flows

Resource owner password credentials flow

Directly inspired by basic authentication, the resource owner password credentials grant (abbreviated ROPC) is probably the simplest OAuth 2.0 flow: the client application asks the user his username/password, sends a token request to the authorization server with the user credentials (and depending on the client authentication policy defined by the authorization server, its own client credentials) and gets back an access token it can use to retrieve the user's resources.

1
2
3
4
5
POST /connect/token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w
1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"bearer",
"expires_in":3600
}

Though not recommended by the OAuth 2.0 specification as it's the only flow where the user password is directly exposed to the client application (which breaks the principle of least privilege), the resource owner password credentials grant is one of the most popular flows.

It is is particularly appreciated by SPA writers as it's trivial to implement in vanilla JavaScript, doesn't involve any redirection or consent form and, unlike interactive flows, doesn't require implementing token validation or cross-site request forgery (XSRF) countermeasures to prevent session fixation attacks.

If you don't feel comfortable with the other flows (and the security measures they require on the client-side), using ROPC might be a good option: paradoxically, a client application using ROPC will be far less vulnerable than a client using interactive flows without implementing the appropriate security checks.

As recommended by the OAuth 2.0 specification, you SHOULD NEVER use it with third-party applications. If you want to support applications you don't fully trust, consider using an interactive flow instead (e.g authorization code or implicit flow).

Read more