Write an OpenID Connect server in three simple steps

An OpenID Connect server is just an OAuth 2.0 server on steroids. What it does it to return the ID Token, which contains information about the authentication event for the user at the door, in addition to the Access Token. This article explains how to write a simple OpenID Connect server using an OAuth 2.0 server as the basis.

1. Add OpenID Connect request / response handling to Authorization Endpoint

Assuming you have OAuth 2.0 server code, what you need to add on top of it is code to handle a few extra parameters used for user authentication.

When a client wants to authenticate the user, it will pass the following information (the green ones are required for OAuth and the red portion is specific to OpenID Connect):

  • client_id: the client id of the application;
  • redirect_uri: https endpoint to which the user should be returned;
  • scope: openid;
    • The scope can also have email, profile, address, phone, offline_access values.

  • response_type: one of “id_token“, “token id_token“, “code“;

The client may also pass these.

  • prompt: Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for reauthentication and consent. Defined values are none, login, consent, select_account.
  • max_age: Specifies the allowable elapsed time in seconds since the last time the End-User was actively authenticated.

On the server side, you have to do the following upon receipt. In addition to the normal OAuth processing:

  • Check if scope includes openid. If it does not, just proceed with normal OAuth processing; 
  • Find out what response_type the request was using;
  • Create the id_token as follows:
    • Make the JSON with following parameters:
      • iss: https URI that indicates the issuer;
      • sub: identifier of the user at the issuer;
      • aud: client_id of the requesting client;
      • nonce: the nonce parameter value received from the client;
      • exp: expiration time of this token;
      • iat: time when this token was issued;
      • auth_time: time the authentication happened;
      • at_hash: the first half of a hash of the access token;
    • Example: 
        {
         "iss": "https://server.example.com",
         "sub": "alice",
         "aud": "clinet-id-received",
         "nonce": "n-0S6_WzA2Mj",
         "exp": 1311281970,
         "iat": 1311280970,
         "auth_time": 1311280969,
         "at_hash": "MTIzNDU2Nzg5MDEyMzQ1Ng"
        }
    • Sign it with JSON Web Signature (JWS) using your RSA private key. You should use a library for JWS. The result is the ID Token to be returned.
  • If the response type is ‘code‘:
    • Save the id_token and send the code back as usual in OAuth.
  • Else if the response type is ‘id_token‘:
    • Send the id_token back to the redirect_uri in the fragment.
  • Else if the response type is ‘token id_token‘:
    • Send the Access Token and ID Token back to the redirect_uri in the fragment.
  • If an error occurs: You have already implemented OAuth error responses, have not you?

Unless the response type was ‘code’, you are basically done here.

2. Make Token Endpoint capable of returning ID Token

To handle a request with the response_type ‘code’, you need to add functionality to the Token Endpoint, which returns the previously created ID Token.

  • When you received the ‘code’, check if it is associated with a previously created ID Token. It could be that ‘code’ has a special strtucture that indicates it or you might pull it from the database you created. 
  • Pull the associated ID Token out from wherever you have stored it and return it in the JSON response with the name “id_token”, e.g.:
      {
       "access_token": "SlAV32hkKG",
       "token_type": "Bearer",
       "refresh_token": "8xLOxBtZp8",
       "expires_in": 3600,
       "id_token": "previously.created.id_token"
      }

    The only addition to the normal OAuth 2.0 is this single parameter, id_token.

  • refresh_token should be returned if the scope included offline_access.
  • If an error occurs: this is just yet another OAuth 2.0 error response that you have already implemented.

3. (Optional) A protected resource: UserInfo Endpoint

You can actually return all the requested claims in the ID Token, but since you might want it to be small for one reason or another, you might want to return most of them from a protected resource. We have standardized resource as the UserInfo Endpoint. It is an OAuth 2.0 protected resource. Most servers will probably implement this but it is not strictly required.

The client accesses the UserInfo endpoint using the Access Token returned to it.  What server needs to do is:

  • Verify that the Access Token is valid; 
  • Pull the claims about the user associated with the Access Token out of its database;
    • The claim set will be one of email, profile, address, phone or a combination of them. The scope elements above map to the following sets of claims:
    • emailemail and email_verified;
    • profilename, family_name, given_name, middle_name, nickname, preferred_usernameprofile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at.
    • address: address, which is a structure with the following members:
      • formatted, street_address, locality, region, postal_code, country;
    • phonephone_number and phone_number_verified;
  • Return them as JSON, e.g.:
      {
       "sub": "alice",
       "name": "Alice de Wonderland",
       "given_name": "Alice",
       "family_name": "de Wonderland",
       "email": "alice@example.com",
       "picture": "http://example.com/alice/me.jpg"
      }
  • If an error occurs, just return the OAuth 2.0 error.

That’s it.

There are additional features in OpenID Connect, but these are the minimal things that you need to implement as a server.

Why is there an ID Token?

As you can see, it is nearly as simple as it could be. One alternative design would be to get rid of the ID Token and create another endpoint.

We actually have started off there. We had a check_id endpoint at the beginning that returned the content of what we have as ID Token now. We moved to ID Token model later for the following reasons:

  • Some social network clients could not tolerate the user experience degradation caused by the round trip time required to use the check_id endpoint; 
  • Some clients needed a token that can be used to create a session. The ID Token serves this purpose.
  • Some clients needed to have an integrity check on access_token; we could implement it using the ID Token.

Also, we could have the returned id_token as the access token, many implementers opposed that; they wanted to keep doing what they were already doing with their access tokens.

Thus, the ID Token was the most straightforward solution. Indeed, we have made it as simple as possible, but no simpler. 😉

31 Replies to “Write an OpenID Connect server in three simple steps”

  1. I have registrered in order to get a password but after 1 day, I don’t have any password to read the post . Should I ask for a password in any other place ? Thanks

  2. Nice article. I was wondering on what grant type the authorization requests are based. Would that be Resource Owner Password Credentials or Client Credentials?

    1. You need to save what is to be in ID Token somewhere until you send the ID Token out.
      Somewhere can be on the server where “code” is being managed, or inside the “code” itself, in which case the server can be more-or-less stateless.
      Once the server send the ID Token out, it does not need to save it anywhere.

  3. Pingback: My Homepage
  4. I think what you wrote was very reasonable. But, think about
    this, what if you were to write a awesome headline? I ain’t suggesting your
    information is not good, however what if you added something that
    makes people want more? I mean Write an OpenID Connect server in three simple
    steps | .Nat Zone is kinda plain. You ought to peek at Yahoo’s home page and note how they
    create post titles to get viewers to click. You might add a
    video or a related pic or two to get readers interested about everything’ve written. Just my opinion, it might
    bring your website a little livelier.

  5. Thank you for every other magnificent article. The place else may
    just anybody get that kind of information in such a perfect approach of writing?
    I have a presentation next week, and I am on the look for such
    information.

  6. I know this if off topic but I’m looking into starting
    my own weblog and was curious what all is required to get set up?
    I’m assuming having a blog like yours would cost a
    pretty penny? I’m not very web smart so I’m
    not 100% positive. Any suggestions or advice would be greatly
    appreciated. Thanks

    1. It does cost a bit more than a penny but that is the price for being self-sovereign, I guess.
      I am now using AWS for this site, but I am using DigitalOcean for other sites and they provide quite an attractive price.

  7. Hello, I think your site might be having browser compatibility issues.
    When I look at your website in Opera, it looks fine but when opening in Internet
    Explorer, it has some overlapping. I just wanted to give you a quick heads up!
    Other then that, superb blog!

  8. Having read this I believed it was very informative. I appreciate
    you finding the time and energy to put this content together.

    I once again find myself personally spending a significant amount of time both reading and leaving comments.
    But so what, it was still worthwhile!

  9. I blօg quite often and Ӏ serіously thank you for your information. Your article has
    truly рeaked my interest. I am ɡoing to boоk mark yoսr website and keеp checking fߋr new details about once a week.
    I sսbscribed to your Feed as well.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.