OpenID Connect in a nutshell

When you read the OpenID Connect Specifications, you might feel a little bit intimidated. That’s because they are written in “spec language” and they deal with corner cases, etc.  Yet when you translate them into normal English and just concentrate on a “simple case”, it becomes quite simple. So, here we go! (OK, much of the text is the same as the original proposal written by David Recordon. It was mostly just a matter of tweaking some of the parameter names, etc.)

Making an OpenID Connect request

In order for the client to make an OpenID Connect request, it needs to have the following information about the server:

  • client identifier – An unique identifier issued to the client to identify itself to the authorization server.
  • client secret – A shared secret established between the authorization server and client used for signing requests.
  • end-user authorization endpoint – The authorization server’s HTTP endpoint capable of authenticating the end-user and obtaining authorization.
  • token endpoint – The authorization server’s HTTP endpoint capable of issuing access tokens.
  • user info endpoint – A protected resource that, when presented with a token by the client, returns authorized information about the current user.
  • check id endpoint – A protected resource that, when presented with an id token by the client, checks the signature and returns information about the user’s session. (Deleted 2012/3/3 : it may come back as a generic OAuth token introspection endpoint)

This information is either obtained by the client developer, having read the server’s documentation and pre-registered their application, or by performing Discovery and Dynamic Registration.

The client constructs a regular OAuth 2.0 request to obtain an access token.

To turn an OAuth 2.0 request into an OpenID Connect request, simply include openid as one of the requested scopes. The openid scope means that the client is requesting an identifier for the user as well as the authentication context. If you want user’s profile URL, name, and picture etc., you can add the additional scope: profile. The server (and user) may choose to make more or less profile information available to the client. If the client also wants the user’s email address, it should include the scope email. The same goes for address (address) and phone (phone).

For example:

GET /authorize?grant_type=token%20id_token&scope=openid%20proflie&
     redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

While pre-registering your client with servers is not required, it’s likely that servers will have varying policies and requirements placed upon clients when it comes to accessing user information.


Receiving an OpenID Connect response

Assuming the user authorized the client’s request, the client will obtain an access token. The OAuth 2.0 access token response will typically include two parameters: access_token and id_token. The following information is encoded as a JSON object in the id_token:

  • aud (audience) – REQUIRED. The client_id that this id_token is intended for.
  • exp (expiration) – REQUIRED. The time after which this token must not be accepted.
  • sub – REQUIRED. A locally unique and never reassigned identifier for the user (subject). e.g. “24400320” or “AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4”.
  • iss (issuer) – REQUIRED. A https: URI specifying the fully qualified host name of the issuer, which when paired with the user_id, creates a globally unique and never reassigned identifier.
    e.g. “https://aol.com”, “https://google.com”, or “https://sakimura.org”.
  • nonce – REQUIRED. nonce value sent in the request.

The id_token parameter is a simple way to make sure that the data received by the client through the User-Agent flow (or other untrusted channels) has not been modified. It is signed by the server using the client secret that was previously established over a trusted channel. It is the concatenation of a base64url encoded signature description (the algorithm), a period (.), a base64url encoded JSON object,  a period (.), and a signature string. This encoding is called a JSON Web Token (JWT). Note that base64url encoding uses two different characters from base64 and no padding.

An authorization server MUST only issue assertions about user identifiers within its domain. The client MUST verify that the aud matches its client_id and iss matches the domain (including sub-domain) of the issuer of the client_id. The authorization server is responsible for managing its own local namespace and enforcing that each user_id is locally unique and never reassigned.

When the client stores the user identifier, it MUST store the tuple of the user_id and iss. The user_id MUST NOT be over 255 ASCII characters in length.

The client SHOULD verify the signature. If the client does not verify the signature, it MUST make a call to the check id endpoint to verify it.

ALL OF WHAT FOLLOWS ARE OPTIONAL. 


Accessing user information

The user info endpoint is a regular OAuth 2.0 resource that returns a JSON document when fetched via HTTP and passed an access token. The client constructs an HTTPS “GET” request to the user info endpoint and includes the access token as a parameter.

The response is a JSON object that contains some (or all) of the following reserved keys:

  • sub – e.g. “AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4”.
  • profile – URL of the End-User’s profile page
  • name – display name of the user, e.g. “Nat Sakimura”.
  • given_name – e.g. “Nat”.
  • family_name – e.g. “Sakimura”.
  • email – e.g. “sakimura@example.com”.
  • picture – e.g. “http://graph.facebook.com/sakimura/picture”.

The server is free to add additional data to this response (such as Portable Contacts) so long as they do not change the reserved OpenID Connect keys. (Note: there are more defined keys, but for the sake of brevity, I am omitting them.)


Discovery

When using OpenID Connect, it’s likely that the client will both have buttons for popular servers as well as a text field for user entry of an email address or URL. (OpenID Connect does not directly solve the “NASCAR” problem.)

The goal of the discovery and registration phase is for the client to obtain the server’s authorization endpoint URL,  token endpoint URL, a client identifier, a client secret, and the user info API endpoint URL. If the client has pre-registered with the server, then this information will already be known. Otherwise the client will have to discover them through Discovery.

  1. The user clicks a button on the client to select a server. In this case the client will have selected a set of preferred servers and thus already knows their authorization endpoint URLs (among possibly other things). The client may or may not already be pre-registered.
  2. The user (or a User-Agent acting on their behalf) enters a URL or email address. In this case, the client needs to perform discovery and determine if there is a valid server endpoint URLs.

Steps:

1) Parse the user input to find out if it is an email address or a URL. If it is email address, do nothing. If no scheme, assume https.

2) Create a canonicalized identifier called “principal” by reconstructing the various parts. For example:

https://joe.example.com -> https://joe.example.com/
example.com -> https://example.com/
joe@example.com -> joe@example.com

3) Extract the domain and make a Webfinger call  over TLS/SSL.

  GET /.well-known/webfinger
    ?resource=acct%3Ajoe%40example.com
    &rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer
    HTTP/1.1
  Host: example.com

  HTTP/1.1 200 OK
  Content-Type: application/jrd+json

  {
   "subject": "acct:joe@example.com",
   "links":
    [
     {
      "rel": "http://openid.net/specs/connect/1.0/issuer",
      "href": "https://server.example.com"
     }
    ]
  }

4) To obtain the specific endpoint URL etc., the client appends “/.well-known/openid-configuration” to the issuer, and GETs the issuer’s configuration file over TLS/SSL as follows:

GET /.well-known/openid-configuration HTTP/1.1
Host: server.example.com

The response is a JSON object that includes endpoint and other information. For example:

{
 "authorization_endpoint": "https://server.example.com/connect/authorize",
 "issuer" : "https://server.example.com",
 "token_endpoint": "https://server.example.com/connect/token",
 "token_endpoint_auth_types_supported": ["client_secret_basic", "private_key_jwt"],
 "userinfo_endpoint": "https://server.example.com/connect/user",
 "check_id_endpoint": "https://server.example.com/connect/check_id",
 "registration_endpoint": "https://server.example.com/connect/register"
 }

 


Unregistered clients and dynamic registrations

Regardless of the discovery mechanism used, the client may or may not already be registered with the server. Servers may have different policies about what data clients can access based on whether they’ve pre-registered (which generally includes agreeing to ToS) versus using a dynamic registration.

If the client does not have a valid client identifier and secret, it can make the following HTTPS “POST” request to the server’s registration endpoint URL (see Discovery) with the following REQUIRED parameters in JSON format as the POST body:

  • redirect_uris – The array of URLs the client is registering with the server for receiving OpenID responses.

For example:

POST /connect/register HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: server.example.com 

{
   "redirect_uris":
     ["https://client.example.org/callback",
      "https://client.example.org/callback2"]
}

Before responding, the server should check to see if the redirect URL is pre-registered outside of this OpenID flow. If so, an error response should be sent. Servers will need to develop a policy to handle what happens when a redirect URL is pre-registered by a developer but has already been used to create dynamic registrations. This might mean, for instance, that new dynamic registrations with that redirect URI will result in an error but requests using existing dynamic registrations continue working until they expire.

To issue a dynamic association, the server includes the following response parameters as JSON:

  • client_id – The client identifier. This could change with each response, which is up to the server.
  • client_secret – The client secret. This should change with each response.
  • expires_at – The number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the client_id and client_secret will expire or 0 if they do not expire.
  • registration_client_uri – The uri to operate on this registration data.
  • registration_access_token – The access token to be used to access the registration_client_uri.

The client should store their dynamic registrations based off of the server’s token endpoint URL. With each dynamic registration, the client will store the client identifier, client secret, expiration time, user endpoint URL, supported flows, and user info API endpoint URL. The expiration time should be stored as an absolute time or an indication that it lasts forever.


As you can see, the basic web client flows for OpenID Connect are quite straightforward, and nearly as simple as those that were originally proposed.  While additional functionality can be used, such as requesting specific sets of claims rather than the default set, having these additional capabilities available for when they’re needed don’t make simple interactions any more complex for clients, which will vastly outnumber the number of OpenID Providers.

 

(Revision History)

  • 2012-01-20 Initial version
  • 2013-01-31 Changed user_id to sub to reflect the change in the specs. 
  • 2013-03-03 Deleted check_id endpoint. Updated discovery and registration to match with the current draft.

39 Replies to “OpenID Connect in a nutshell”

  1. Good post Nat! It would be helpful if you clarified whether or not clients should verify the id_token and also call the user_info endpoint after receiving an assertion. 

    1. I will. The client should verify the id_token by itself (if capable of doing crypto itself), or call check id endpoint to verify it. It does not have to call userinfo endpoint unless it wants more “claims.”

          1. What are we gaining by verifying the id_token ? To make the userinfo call, we anyways use the access token .

  2. This is a great summary, and I think it helps to clarify many of the moving pieces from the clients’ perspective. I think it’d be helpful to get something similar from a server side. “So you want to be an IdP?”

  3. What about the client checking that the aud (audience) field is actually for this client, and not another one being replayed?

  4. At least part of the confusion comes from the terminology such as “Client” which is overloaded. For example, the service provider term of SAML roughly (but not exactly if you want to be pedantic) corresponds to Client; and IdP corresponds to server.

  5. Could you add a paragraph at the beginning to say what OpenID connect actually is? Is it OpenID 3.0 with a cooler name, or is it fundamentally different? Assuming both the website and OpenID provider support it, what difference will a random non-technical user see?

    Something like “OpenID connect is the new version of open ID that in addition to letting you log in also …”

    1. OpenID Connect is a new generation of the internet identity protocol. Technically, it is fundamentally different than OpenID 2.0. From the point of view of the non-technical end user, however, it would be hard to see the difference. 

      This article is written for the technical audience. There is a separate article for non-tech people at http://nat.sakimura.org/2011/05/15/dummys-guide-for-the-difference-between-oauth-authentication-and-openid/ . 

  6. I am just wondering if this is an error or not, but is it, “The User”, that is doing the authorizing, or is that an error? The user is, “Being Authorized”, not “Doing the Authorizing”? I think it is an OAuth 2 Server doing the Authorizing? I will keep reading.

      1. Thanks, After thinking about it (and reading your response) I realized that the client has to be authorized by the user (that is the entire Federation idea.) I was in the middle of setting up a non federated client as resource owner with IdentityServer3 (OpenID Connect server), but am kind of new to all the flows/scopes. I have increased my understanding of this now. Also was able to get my resourceowned Client flow, (So yea my first case is probably OAuth flow really) from my ( authenticated MVC to client webapi) working. Ok now on to Federated cases. Hopefully my OpenID Connect vocabulary will improve shortly. I know the words are important, as I use to do conformance testing for Routing Protocols (RIP, OSPF, BGP, etc…) before my Dev days.

        Thanks

  7. That is very fascinating, You’re an excessively skilled blogger.
    I have joined your feed and sit up for looking for extra
    of your excellent post. Also, I have shared your website in my social
    networks

Leave a Reply

Your email address will not be published. Required fields are marked *