Getting started with OpenID Connect

Introduction

This post is a snapshot of my experience trying to implement OpenID Connect flows for authentication and authorization using the Katana Project v3 RC2 middleware and IdentityServer v3 beta 1.

The first thing that is important to realise is that OpenID Connect has its own specification (which you should read) and should be treated as distinct from its precursors such as OAuth 1.0, OAuth 2.0 and OpenID.

My goal was to set up an OpenID Connect authorization code flow using available pluggable components.  This has meant trying out pre-release code and my conclusion (in August 2014) is that this isn't something you can do yet, without non-trivial custom coding.

Terminology Muddle

Conceptually we are trying to achieve two things:

  • Authenticate the user
  • Authorize the authenticated user to do something
But you will run into overloaded or confusing terminology everywhere.

For example:

The OpenID Connect specification initially starts by defining the thing that does authentication and authorization as the "OpenID Provider" (which I think could anyway be better named the "OpenID Connect Provider" to avoid confusion with previous OpenID specifications, which also define an OpenID Provider).

When going into the detail of the flows, the specification then starts using the term "Authorization Server" instead of "OpenID Provider" and then makes statements like "Authorization Server Authenticates the End-User", which to me is not helpful in making it clear what role each component plays.

So for the purposes of this article I will use the term OpenID Connect Provider abbreviated to OCP to refer to the component(s) responsible for authentication and authorization in OpenID Connect flows.

Available Components

My goal was to implement the OpenID Connect Authorization Code Flow using pluggable components, without much custom coding, to give browser-based access to a web-application.

The components I chose to investigate are introduced below.  The main thing to note is that although Google, for example, has "aligned" its login process with OpenID Connect, at the current time it is still new and everything I am looking at is pre-release.

Microsoft's OpenID Connect OWIN Middleware

I added the Microsoft.Owin.Security.OpenIdConnect 3.0.0-rc2 (pre-release) NuGet package (see the Katana Project for details).

Then I was able to wire up the OpenIdConnectAuthenticationMiddleware using the supplied extension.  The outline pattern being:
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            [....]
            
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    [....]
                });
So what does this middleware do?  Well, in order to make it easy to plumb in, it seems to have the ambition of doing "everything" relating to OpenID Connect flows.  In other words, detecting when a user needs to be redirected to the OCP, and then handling the various interactions with the OCP required.

I say ambition because this initial (pre) release has very limited functionality and flow-support, as I will describe below.

Thinktecture OpenID Connect Provider

Dominick Baier is a bit of a guru on authentication and authorization and has has an OpenID Connect Provider which he calls Identity Server v3 (not to be confused with his Identity Server v2 for OAuth2).  As of 1st August 2014 this is at Beta 1 release.

The purpose of Identity Server 3 is to provide authentication and authorization, which the OWIN middleware can interact with, following pre-defined flows.

What works out of the box?

A fairly early realisation is that my goal of implementing an authorization code flow was not possible out of the box. 

In broad terms the flows supported at this point in time are as follows:

Protocol OpenIdConnectAuthenticationMiddleware  IdentityServer 3

Implicit Flow
 
Yes Yes

Code Flow
 
No Yes

Hybrid Flow
 
Yes No

This does mask a lot of detail, which is set out in the sections below, based on what I found by trial and error.

Flows Supported by OpenIdConnectAuthenticationMiddleware

Implicit Flow is easy for the middleware to support, as it just has to redirect the user to the OCP, then its role is complete. As described below, this works fine.

It took a while debugging source code to discover that the OpenIdConnectAuthenticationMiddleware only supports hybrid-flow in the first version, and not (the simpler) code-flow.

The clue was found in OpenIDConnectAuthenticationHandler.AuthenticateCoreAsync() method:
// code is only accepted with id_token, in this version, [...]
// OpenIdConnect protocol allows a Code to be received without the id_token

if (string.IsNullOrWhiteSpace(openIdConnectMessage.IdToken))
{
   _logger.WriteWarning("The id_token is missing.");
    return null;
}

Therefore the response from the OCP must include the access-code and the ID token, which is (according to the OpenID Specification), hybrid-flow.

Hybrid Flow And Hash Fragments

The specification says Hybrid Flow should return the access code and token(s) in the hash-fragment of the URL, which is meant for a JavaScript client to read.

So how does the (server-side) middleware receive the access-code and token(s) in this case?

The answer seems to be that the JavaScript agent should parse the hash-fragment and take anything it needs (the access-token, perhaps) and then re-package the access-code and token(s) and HTTP-post them to the Client, which the OWIN middleware can then extract and process.

The OpenID Connect specification actually gives a "non-normative" example of doing this in:

15.5.3 Redirect URI Fragment Handling Implementation Notes

The OpenIdConnectAuthenticationMiddleware seems to expect this to be the process used, as the only Response Mode it supports is:

  • "form-post"
The OpenID Connect specification also allows for "query" & "fragment" but these are not supported in this version of the middleware (see http://katanaproject.codeplex.com/workitem/313)

Flows Supported by IdentityServer 3

From AuthorizeRequestValidator.ValidateProtocol we can see that two flows are supported:
  • Code Flow
    • Response Types
      • "code"
  • Implicit Flow
    • Response Types
      • "token" (legacy from OAuth2, don't use with OpenID Connect)
      • "id_token"
      • "id_token token"
//////////////////////////////////////////////////////////
// match response_type to flow
//////////////////////////////////////////////////////////
if (_validatedRequest.ResponseType == Constants.ResponseTypes.Code)
{
  Logger.Info("Flow: code");
  _validatedRequest.Flow = Flows.Code;
  _validatedRequest.ResponseMode = Constants.ResponseModes.Query;
}
else if (_validatedRequest.ResponseType == Constants.ResponseTypes.Token ||
         _validatedRequest.ResponseType == Constants.ResponseTypes.IdToken ||
         _validatedRequest.ResponseType == Constants.ResponseTypes.IdTokenToken)
  {
    Logger.Info("Flow: implicit");
    _validatedRequest.Flow = Flows.Implicit;
    _validatedRequest.ResponseMode = Constants.ResponseModes.Fragment;
  }

IdentityServer3 does not yet support Hybrid Flow.

Implicit Flow Configuration

Implicit Flow has a number of key defining features:
  • It is intended to be used by something like a JavaScript Single Page Application (SPA) which needs to communicate with the OCP directly.
  • The SPA will retrieve and store the token(s) and subsequently use them when communicating with (say) an API.
  • The ID token (& access token if requested) is delivered in the hash-fragment of the URI, which means the tokens can only be read in the browser.  The format would be something like:
    https://localhost/Client/#id_token=eyJ0eXAi[...]&access_token=eyJ0eX[...]
  • Refresh (offline) tokens are not supported.

OpenIdConnectAuthenticationMiddleware

The OpenID Connect Specification (3.2.2.1) says that the Response Type for implicit flow can be specified as either
  • id_token (just return the ID token)
  • id_token token (also return the access token)
So the middleware can be configured for the second case, as follows:
 public partial class Startup
 {
     public void ConfigureAuth(IAppBuilder app)
     {
         app.SetDefaultSignInAsAuthenticationType("External Bearer");
            
         app.UseOpenIdConnectAuthentication(
             new OpenIdConnectAuthenticationOptions
             {
                 ClientId = "implicitclient",
                 Authority = "http://localhost/IdentityServer3/core/",
                 RedirectUri = "https://localhost/Client/",

                 // "id_token" just returns the ID token.
                 // "id_token token" also returns the ID token and the access token
                 ResponseType = "id_token token",
                 ResponseMode = "fragment",

                 Scope = "openid email",                  
             });
The middleware can auto-configure itself to work with the OCP, provided that the OCP publishes information about itself at:
  • /.well-known/openid-configuration
Identity Server 3 does just that, with the following JSON returned:
{
    "issuer": "https://localhost/IdentityServer3",
    "jwks_uri": "http://localhost/IdentityServer3/core/.well-known/jwks",
    "authorization_endpoint": "http://localhost/IdentityServer3/core/connect/authorize",
    "token_endpoint": "http://localhost/IdentityServer3/core/connect/token",
    "userinfo_endpoint": "http://localhost/IdentityServer3/core/connect/userinfo",
    "end_session_endpoint": "http://localhost/IdentityServer3/core/connect/endsession",
    "scopes_supported": ["openid","profile","email","read","write","offline_access"],
    "response_types_supported": ["code","token","id_token","id_token token"],
    "response_modes_supported": ["form_post","query","fragment"],
    "grant_types_supported": ["authorization_code","client_credentials","password","implicit"],
    "subject_types_support": ["pairwise","public"],
    "id_token_signing_alg_values_supported": "RS256"
}
Thus the first thing the Katana middleware does is to visit this endpoint and configure itself accordingly.

IdentityServer 3

IdentityServer 3 is in beta stage at the time of writing, and the configuration to register / define a Client is just hard-coded into a class called Clients.  The relevant entry for our implicit flow Client is:
  new Client
  {
      ClientName = "Implicit Clients",
      Enabled = true,
      ClientId = "implicitclient",
      Flow = Flows.Implicit,
     
      ClientUri = "https://localhost/Client/",
      [...]
 
      RequireConsent = true,
      AllowRememberConsent = true,
     
      RedirectUris = new List<Uri>
      {       
          // JavaScript client
          new Uri("http://localhost:21575/index.html"),
 
          // OWIN middleware client
          new Uri("https://localhost/Client/")
      },
     
      ScopeRestrictions = new List<string>
      {
          Constants.StandardScopes.OpenId,
          Constants.StandardScopes.Profile,
          Constants.StandardScopes.Email,
          "read",
          "write"
      },
 
      IdentityTokenSigningKeyType = SigningKeyTypes.Default,
      SubjectType = SubjectTypes.Global,
      AccessTokenType = AccessTokenType.Jwt,
     
      IdentityTokenLifetime = 360,
      AccessTokenLifetime = 360,
  },

Time to login!

Something needs to start the login process, so I just created a dummy controller action with an [Authorize] attribute.  Trying to call the corresponding URL while not logged in means the OWIN middleware will redirect you to the start of the login process.

And it works.

Identity Server 3 provides us with a login box, followed by a consent screen:



Before finally returning the requested tokens in the hash-fragment of the redirect URI:
https://localhost/Client/#id_token=eyJ0eXAi[...]&access_token=eyJ0eX[...]
To make this flow useful we would now need to write some JavaScript to get the tokens from the hash-fragment, store them, then send them with each request to our API.

Authorization Code Flow Configuration

Code Flow has a number of key defining features:
  • The user goes through the same steps of a login screen and consent screen, as for the implicit flow described above.
  • But instead of sending the token(s) back at the end of this process, the OCP returns an "Authorization Code" instead.
  • The (server-side) Client receives this code then logs into the OCP directly and exchanges this code for the ID token and access token.
  • The Client then uses the token(s) on behalf of the user without the user-agent (eg a browser) ever handling the token(s) directly.
  • The Client can also request a refresh (offline access) token, which it can use to get new access tokens from the OCP when tokens expire.
This isn't fully supported / implemented with the components I have looked at, so the following sections show how I arrived at that conclusion.

OpenIdConnectAuthenticationMiddleware

The OpenID Connect Specification (3.1.2.1) says that the Response Type for code flow must be specified as
  • code
This is how I tried to configure the Katana Middleware:
 public partial class Startup
 {
     public void ConfigureAuth(IAppBuilder app)
     {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType
            });
            
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = "codeclient",
                    ClientSecret = "secret",
                    Authority = "http://localhost/IdentityServer3/core/",
                    RedirectUri = "https://localhost/Client",

                    ResponseType = "code",
                    ResponseMode = "query",
                    Scope = "openid email",

                    SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,                
                });
The middleware can auto-configure itself for the OCP, as before.

The flow won't get far enough to worry about the cookie configuration, but my intent is that the middleware retrieves the tokens then stores them in session for the user.   That way, the tokens never leave the server and are just related to the user via the session, with the session cookie round-tripping to the user-agent as normal.

IdentityServer 3

The hard-coded Client configuration is this time:
  new Client
  {
      ClientName = "Code Flow Clients",
      Enabled = true,
      ClientId = "codeclient",
      ClientSecret = "secret",
      Flow = Flows.Code,
      
      RequireConsent = true,
      AllowRememberConsent = true,
      
      ClientUri = "https://localhost/Client",
      [...]
      
      RedirectUris = new List<Uri>
      {
          // OWIN middleware client
          new Uri("https://localhost/Client")
      },
      
      ScopeRestrictions = new List<string>
      {
          Constants.StandardScopes.OpenId,
          Constants.StandardScopes.Profile,
          Constants.StandardScopes.Email,
          Constants.StandardScopes.OfflineAccess,
          "read",
          "write"
      },
      
      IdentityTokenSigningKeyType = SigningKeyTypes.Default,
      SubjectType = SubjectTypes.Global,
      AccessTokenType = AccessTokenType.Reference,
      
      IdentityTokenLifetime = 360,
      AccessTokenLifetime = 360,
      AuthorizationCodeLifetime = 120
  },
Note the ClientId and ClientSecret which allows the Client to login to the OCP (over TLS) before requesting the token(s).

Time to login!

The first part of the login process works fine.  We get a login box and consent screen, as before.  And in line with the OpenID Connect specification, we get an authorization code sent back to the Client, in the query string, in the format:
https://localhost/Client?code=e92f6e[...]=OpenIdConnect.AuthenticationProperties=fkVPi[...]
Code Flow then expects the Client to contact the OCP directly, and exchange the code for the ID token and access token.

But this doesn't happen.  The OpenIdConnectAuthenticationMiddleware doesn't include any code to handle this type of response.

Why doesn't it work?

As described in "What works out of the box?" above:
  • The only flow supported by this version of the middleware is Hybrid Flow, with the access-code and ID token returned to the Client in a form post.
  • The only flows supported by the beta version of IdentityServer3 are Code Flow, with the access-code returned in the Query String and Implicit Flow, with the token(s) returned in the Hash Fragment.  
​So there is a mismatch both in the flows supported and the return types supported, and clearly code-flow is not possible out of the box.

Supported Response Modes

Subsequent to my tests (above) the Katana team realised it was misleading to allow the middleware to be configured to request a response in the Query String, since this is not supported:
ResponseMode = "query",
So as of this closed Katana issue the ResponseMode is now defaulted to "form_post" and the ResponseMode property has been removed from the configuration.

Section 13 of the OpenID Connect specification says it should be possible to serialize messages using any of the following:

  • Query String Serialization
  • Form Serialization
  • JSON Serialization
So I would expect this ResponseMode configuration to be re-instated in future versions.

Hybrid Flow Configuration

As the name suggests, Hybrid Flow has some of the characteristics of Code Flow (an access code is returned) and some of Implicit Flow (an ID token and access token can be returned).

The access code and tokens are returned in the URL's hash-fragment to the User Agent.  So if the Client also requires the access-code or token(s) the User Agent has to extract & repackage the items (eg in JavaScript) and POST them on to the (server-side) Client.

OpenIdConnectAuthenticationMiddleware

The OpenID Connect Specification (3.3.2.1) says that the Response Type for hybrid flow must be specified as any of:
  • code id_token
  • code id_token token
  • code token (legacy from OAuth2, don't use with OpenID Connect)
The variants controlling whether the ID token, access-token, or both are returned.

So the middleware would be configured to return everything, as follows:
 public partial class Startup
 {
     public void ConfigureAuth(IAppBuilder app)
     {
         app.SetDefaultSignInAsAuthenticationType("External Bearer");
            
         app.UseOpenIdConnectAuthentication(
             new OpenIdConnectAuthenticationOptions
             {
                 ClientId = "hybridclient",
                 Authority = "http://localhost/IdentityServer3/core/",
                 RedirectUri = "https://localhost/Client/",

                 ResponseType = "code id_token token",
                 ResponseMode = "fragment", // maybe "form_post"?

                 Scope = "openid email",                  
             });

There is implementation to process the ID token received and create an AuthenticationTicket in:

OpenIdConnectAuthenticationHandler.AuthenticateCoreAsync

But there is no implementation to convert the access-code into tokens (although an AuthorizationCodeReceivedNotification is published, which you can hook into with your own implementation).

IdentityServer 3

At this point I looked at IdentityServer 3 and found it doesn't yet support Hybrid Flow.  

If you request any of the hybrid flow response types, such as "code id_token token", this is rejected as not supported by the AuthorizeRequestValidator class.

Refresh Tokens

Another reason for wanting to use Code Flow is to allow the use of Refresh Tokens.  

In our case, we have various Client-side processes that need to run on behalf of the user, but without the user's involvement. Therefore if the access-token has expired, the Client needs to be able to request a new one, using the refresh-token.

There is a blog post for IdentityServer 3 that describes the current refresh token support.

Conclusions

Implicit Flow is the only flow which works out of the box, with the components evaluated, at this point in time.

Authorization Code Flow is what I want to use, but this isn't implemented yet in the middleware being evaluated. Fortunately the Katana Project contains other examples, such as GoogleOAuth2AuthenticationMiddleware, which does implement code-flow, so it should be possible to write some custom middleware using the existing code to kick-start things.

If you've found an alternative set of components that work for authorization code flow, please leave a comment!
Comments
Blog post currently doesn't have any comments.
Leave comment



 Security code