Scopes and Claims in OpenID Connect

In OpenID Connect, there are notions of “scopes” and “claims”. Some people see some overlap there and wonders why they are like that. Here is my attempt to explain the relationship between the two.

OpenID Connect defined scopes

OpenID Connect defines several scopes. They are:

  • openid – REQUIRED. Informs the Authorization Server that the Client is making an OpenID Connect request. If the openid scope value is not present, the request MUST NOT be treated as an OpenID Connect request. The openid value also requests that the ID Token associated with the authentication session be returned. If the response_type includes token, the ID Token is returned in the Authorization Response along with the Access Token. If the response_type includes code, the ID Token is returned as part of the Token Endpoint response. This scope value requests access to the user_id Claim at the UserInfo Endpoint.
  • profile – OPTIONAL. This requests that access to the End-User’s profile Claims excluding the address and email Claims at the UserInfo Endpoint be granted by the issued Access Token.
  • email – OPTIONAL. This requests that access to the email and verified Claims at the UserInfo Endpoint be granted by the issued Access Token.
  • address – OPTIONAL. This requests that access to address Claim at the UserInfo Endpoint be granted by the issued Access Token.
  • phone – OPTIONAL. This requests that access to the phone_number Claim at the UserInfo Endpoint be granted by the issued Access Token.

They can be regarded as the shorthand for the full claims in OpenID Request Object.

OpenID Request Object

The OpenID Request Object is used to provide OpenID request parameters that MAY differ from the default ones. Implementing support for the OpenID Request Object is OPTIONAL. Supporting it is necessary for implementations that need to request or provide sets of Claims other than the default UserInfo, and ID Token Claim sets.

The OpenID Request Object is a JWT [JWT] that is passed as the value of the “request” parameter in the Authorization Request. Parameters that affect the information returned from the UserInfo Endpoint are in the “userinfo” member. Parameters that affect the information returned in the ID Token are in the “id_token” member.

It also includes other OAuth parameters. Thus, in case of scope=openid%20profile, then the equivalent request object will look like:

{
 "response_type": "code id_token",
 "client_id": "s6BhdRkqt3",
 "redirect_uri": "https://client.example.com/cb",
 "scope": "openid profile",
 "state": "af0ifjsldkj",
 "id_token":
   {
     "claims":
       {
        "auth_time": null,
       },
   },
 "userinfo":
   {
     "claims":
       {
         "name": null,
         "nickname": {"optional": true},
         "email": null,
         "verified": null,
         "picture": {"optional": true}
       }
   }
}

Mapping OpenID Connect scopes to Connect claims

So let us go ahead and map the scopes to the claims.

openid

The scope openid can be mapped to id_token claim in the request object. That is:

"id_token": {
  "claims": {
    "auth_time": null
  }
}

Note that there are other claims that can go into id_token member, such as max_age. However, just stating “openid” in the scope is equivalent to requesting this claim set. If you need to specify in finer grain, you have to use request object so that the “id_token” claim will look like:

 "id_token":
   {
     "claims":
       {
        "auth_time": null,
        "acr": { "values":["2"] }
       },
     "max_age": 86400,

   }

 profile

Similarly, the “profile” scope is equivalent to request the following claims in the request object.

 "userinfo":
   {
     "claims":
       {
         "user_id": null,
         "name": {"optional": true},
         "nickname": {"optional": true},
         "profile": {"optional": true},
         "picture": {"optional": true},
         "website": {"optional": true},
         "gender":  {"optional": true},
         "birthday":  {"optional": true},
         "locale":  {"optional": true},
         "zoneinfo":  {"optional": true},
         "updated_time": {"optional": true}
       }
   }

email

As expected, ’email’ scope is equivalent to the following:

 "userinfo":
   {
     "claims":
       {
         "email": null,
         "verified": null
       }
   }

address

Similarly, ‘address’ scope is equivalent to the following:

 "userinfo":
   {
     "claims":
       {
         "address":
           {
             "formatted": null
           }
        }
    }

Note that there are other optional claims that may go into “address” claim. They are:

  • formatted – The full mailing address, formatted for display or use with a mailing label. This field MAY contain newlines. This is the Primary Sub-Field for this field, for the purposes of sorting and filtering.
  • street_address – The full street address component, which may include house number, street name, PO BOX, and multi-line extended street address information. This field MAY contain newlines.
  • locality – The city or locality component.
  • region – The state, province, prefecture or region component.
  • postal_code – The zip code or postal code component.
  • country – The country name component.

We have not defined the detailed format of those claims because there is no agreed upon universal address system in the world. I even had a problem in having a claim name like “street_address”. You may think that street address is universal, but it is not true. For example, there is no “street address” defined in Japan. It only has residencial address that is assigned to an area with a house ever built. Often, an American asked for an “address” to get to my house when visiting me. Sorry, no luck. Now that you have Google maps that directs you, you can reach my house that way as well, but before, you could not. It is not defined in terms of street, nor they are numbered in sequence. It is an OID for the piece of land with a house. You had to locate the “address” on a map, then find the street that has no name, and try to get there often by counting the number of roads in between, etc. Different countries have different addressing system. It is best left for each country to define their own claims if more structured address claim is needed.

Conclusion

Hopefully, now you have clear idea of the relationship between the “scope” and “claims”.

OpenID Connect scopes can be thought of as the short hands for predefined sets of claims.  They are handy, but sometimes are too blunt. Under such circumstances, you would have to use claims.

You are free to define your own “scopes”. However, I believe that it is a good practice to define claims that maps to it. A custom “scope” is supposed to be defined as a URL. It would be pretty cool if you put the equivalent claim file to the URL so that the semantics of the “scope” would be machine readable.

2 Replies to “Scopes and Claims in OpenID Connect”

  1. Nat, thanks for this article, it helped us a lot in our implementation.

    One question though: aren’t all scope-derived claim requests supposed to be optional, including the email and address ones?

    I found the following in the latest spec version: http://openid.net/specs/openid-connect-standard-1_0-09.html#openid_scopes

    “The Authorization Server MAY fully or partially ignore the scope values
    requested by the Client based on the Authorization Server policy or
    the End-User’s instructions.”

    This implies that “email”, “verified”, and “address” should also be marked {optional:true}.

Leave a Reply

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