Creating your own OpenID Connect server with ASOS: conclusion

While this quite long blog posts series about ASOS ends here, there are still many aspects to cover. As promised in my introduction post, I'll dedicate a future post to the client-side part. An in-depth post about token revocation and the differences between JWT and opaque tokens is also planned.

If you have questions about ASOS or OAuth2/OpenID Connect, don't hesitate to join us on Gitter.im.

If you need personal assistance, are looking for a contractor or have remarks about this blog posts series, please ping me at contact [at] kevinchalet.com.

drop-the-mic.gif

Creating your own OpenID Connect server with ASOS: testing your authorization server with Postman

The sample used in this post can be found in the AspNet.Security.OpenIdConnect.Samples repository, that also hosts the Cordova, MVC and SignalR samples for ASOS.

For clarity, it implements both the authorization code flow and the password flow, but doesn't use any membership stack (the user credentials are hardcoded in the authorization provider class and a fake identity is always used to create tokens).

To test REST services, one of the easiest options is indisputably to use Postman. If you're not already familiar with Postman, I encourage you to read the documentation.


Retrieving an access token using the resource owner password credentials grant

Using the password flow with Postman is quite straightforward:

  • Select POST as the HTTP method.
  • Fill the Request URL input with the absolute address of the token endpoint.
  • Click on the Body tab and choose the x-www-form-urlencoded encoding.
  • Add the OAuth2 parameters required by the specification, as shown on this screenshot:
password-grant-token-request.png

Read more

Creating your own OpenID Connect server with ASOS: adding custom claims and granting scopes


Attaching a destination to custom claims using AddClaim or SetDestinations

Unlike OAuthAuthorizationServerMiddleware, ASOS doesn't assume that access tokens are always consumed by your own resource servers and refuses to serialize claims that don't explicitly specify a destination to avoid leaking confidential data to unauthorized parties.

Two destinations are currently supported by ASOS: access_token and id_token. There's no equivalent for authorization codes or refresh tokens as they are always encrypted and only readable by the authorization server itself.

Concretely, this means that all your claims won't be returned to the client application, unless you explicitly call the AddClaim overload taking one or more destinations or use SetDestinations to attach the appropriate destination(s) to your claims.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
// When access_token and id_token are specified,
// the claim will be serialized in both tokens.
identity.AddClaim("username", "Pinpoint",
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// If only access_token is specified, the language
// claim won't be added in the identity token.
var claim = new Claim("language", "fr-FR");
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(claim);

Read more

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.

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 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 OAuth2 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