Abstract

OpenID Connect is an interoperable authentication protocol based on the OAuth 2.0 family of specifications. It uses straightforward REST/JSON message flows with a design goal of "making simple things simple and complicated things possible".

Although the standard was ratified only recently, in February 2014, it has already seen widespread adoption, notably by Google and Microsoft as well as salesforce.com.

Salesforce Identity uses OpenID Connect to authenticate users across orgs and providers, reducing the number of passwords and other credentials that users have to manage.

This article describes the OpenID Connect Basic Client Profile as implemented in Salesforce Identity as of Spring '14.

OpenID Connect Basics

OpenIDConnectRoles.png

OpenID Connect allows one or more relying parties to delegate user authentication to an OpenID Provider. The OpenID Provider authenticates users, and provides data, or claims, concerning users to relying parties. These claims are user attributes such as first and last name, email address, and department. As a result, relying parties are freed from the need to run a login process, and users have fewer credentials to manage.

Since OpenID Connect is based on OAuth 2.0, and essentially runs over the same infrastructure, the OpenID Provider is also referred to as the 'authorization server', particularly when discussing protocol flows. For our purposes, the relying party is an app, specifically a connected app, and we'll refer to it as such in this document.

This single sign-on functionality is similar to the older SAML protocol, but, since it is based on JSON rather than XML, OpenID Connect is much easier for developers to integrate. OpenID Connect was designed to also support native apps and mobile applications, whereas SAML was designed only for Web-based applications. SAML and OpenID Connect will likely coexist for quite some time, with each being deployed in situations where they make sense.

Why Not Just Use OAuth 2.0?

You might be asking yourself why we need OpenID Connect at all - after all, OAuth 2.0 provides an access token, which a client app can use to retrieve user information, effectively discovering the identity of the user to whom the token was issued. Why do we need an additional layer of protocol?

The answer is that OAuth 2.0 allows the user to authorize the client app to access resources such as APIs on the user's behalf; the access token is a 'bearer' token allowing the app to make API calls. What happens if we do use the access token to represent the user's identity?

Let's say your account is at ProviderCo, and you are using GoodApp, a mobile app that gives access to some cool functionality on the GoodApp servers. GoodApp uses OAuth 2.0 to obtain an access token from ProviderCo to retrieve your user profile, including attributes such as your name and email address, and (crucially) gives you access to resources based on the fact that it receives that access token.

Now let's imagine that an attacker wants to access your resources at GoodApp. The attacker can make his own app, BadApp. The app itself is completely innocuous - a game, perhaps. The attacker registers BadApp with ProviderCo so that BadApp also receives access tokens to retrieve user profiles.

Here is the problem with using those access tokens to represent authentication: OAuth 2.0 is designed to give apps access to resources; nothing more. Once BadApp has an access token, properly issued to it for the purpose of obtaining the user profile, it can engineer an interaction with GoodApp where it appears to be both the user and ProviderCo, presenting its access token and gaining access to the user's resources at GoodApp. GoodApp has no way of knowing that the token was issued to BadApp - all it sees is a token that gives access to a user profile.

Configuring OpenID Connect for your Application

If you're planning to use Salesforce as an OpenID Provider, you'll need to define a connected app in your organization. Since an OpenID Connect app is just an OAuth 2.0 connected app with the openid scope selected, everything you need to know is in the 'Digging Deeper into OAuth 2.0' article.

Once you've defined your connected app, and you have your client id and client secret, you're all set to integrate. OpenID Connect libraries exist for a wide variety of languages; but it's useful to have an idea of the underlying protocol.

The OpenID Connect Protocol

OpenIDConnectBasicClientProfile.png

The sequence starts (1) with the end-user making some request of the client - for example, clicking a 'Login with Salesforce' button. The client redirects (2) the end-user's browser to a URL at the authorization server of the following form:

https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id=<your_client_id>&redirect_uri=<your_redirect_uri>

The following parameters are required (all must be URL encoded):

response_type Must be set to code to request an authorization code.
client_id Your application's client identifier (consumer key in Connected App Detail).
redirect_uri The end user's browser will be redirected to this URI with the authorization code. This must match your application's configured callback URL.

Optional parameters can be defined as described in Web Server to tailor the login page to the user's device type, avoid user interaction etc.

the user will be prompted to authenticate (3) and, if they have not already done so, authorize the client application. If openid scope is requested, the user is prompted to 'allow access to their unique identifier'.

OpenIDConnectApproval.png

On successful authorization, the user's browser is redirected back to the redirect URI at the client application (4), with a URL of the form:

https://app.example.com/oauth_callback?code=aWe...c4w%3D%3D&state=<whatever_you_sent_in_step_2>

The client application can now extract the authorization code from its URL parameter and send a direct POST request (5) to the authorization server with URL https://login.salesforce.com/services/oauth2/token and payload of the form

code=aWe...c4w==&grant_type=authorization_code&client_id=<your_client_id>&client_secret=<your_client_secret>&redirect_uri=<your_redirect_uri>

The following parameters are required (again, URL encoded):

code The value returned by the authorization server in the previous step.
grant_type Set this to authorization_code.
client_id Your application's client identifier.
client_secret Your application's client secret (consumer secret in the connected app detail page).
client_assertion Instead of passing in client_secret you can choose to provide a client_assertion and client_assertion_type. If a client_secret parameter is not provided, Salesforce checks for the client_assertion and client_assertion_type automatically.



The value of client_assertion must be a typical JWT bearer token, signed with the private key associated with the OAuth consumer’s uploaded certificate. Only the RS256 algorithm is currently supported. For more information on using client_assertion, see the OpenID Connect specifications for the private_key_jwt client authentication method.

client_assertion_type Provide this value when using the client_assertion parameter.

The value of client_assertion_type must be </code>urn:ietf:params:oauth:client-assertion-type:jwt-bearer</code>.

redirect_uri Again, this must match your application's configuration.

If you're familiar with OAuth 2.0, you've probably noticed that, with the exception of the openid scope value, the OpenID Connect Basic Client Profile is so far identical to the OAuth 2.0 authorization code grant type ('web server flow'). This is intentional - OpenID Connect was designed to leverage existing identity infrastructure.

Here is the key difference from 'vanilla' OAuth 2.0: the authorization server's response (6) contains an ID token. (Line breaks inserted for legibility).

{
	"id":"https://login.salesforce.com/id/00D50000000IZ3ZEAW/00550000001fg5OAAQ",
	"issued_at":"1296458209517",
	"scope": "openid",
	"instance_url":"https://na1.salesforce.com",
	"token_type": "Bearer",
	"refresh_token":"5Aep862eWO5D.7wJBuW5aaARbbxQ8hssCnY1dw3qi59o1du7ob.lp23b
a_3jMRnbFNT5R8X2GUKNA==",
	"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4OCJ9.eyJleHA
zOTY5Nzk2ODEsInN1YiI6Imh0dHBzOi8vcHJlcmVsbG9naW4ucHJlLnNhbGVzZm9yY2UuY29tL2lk
LzAwRHgwMDAwMDAwQTl5MEVBQy8wMDV4MDAwMDAwMFVuWW1BQUsiLCJub25jZSI6ImVyaWV3cmlld
3J3amtiamtlaHJld29nciIsImF1ZCI6IjNNVkc5bEtjUG9OSU5WQkxXSm5CX1lydFBERTNXeU9wMU
ZrRzRKNmNHbWlFbXZQaXdtNGUxQ2J4UmVlOEVUSDVPODJBWURpS3FTM3FvZ3lKU3hMc24iLCJpc3M
iOiJodHRwczovL3ByZXJlbGxvZ2luLnByZS5zYWxlc2ZvcmNlLmNvbSIsImlhdCI6MTM5Njk3OTU2
MSwiY19oYXNoIjoiZjItYmU3b3VZS2JJcnFUdXNfak5SZyJ9.ax9_Dw02MTh5ZaKVGaLGJzpDLpq3
o_JzOBF6GIWxqtrbEPfK3_mmL8IMxSNt2MqkbCO0_oFkxJW6OMAKSiqCkpIYvWyJz2ENgDu9jDNxL
d81A9RdE4iHvl0ovbA9w7vw0hQNCwhyEah2Zmd5tDrJ7DGMVvck_TDOv5x3LBEyTpLw_qQSZtqU2C
BkD55DsVOdriIoBU73zumrQ-tNe5SqD1-BUB1-U8dWcOV5Fi1RhUyDFKJncoOSKPlvTnu_78O4gn9
z19FjZwQEbHrVOQNk6Lm0CXGGqXe2yAvH_BYQRumORCB9Kooa79dEWLq9t0pDWmmkua-jOoGjFH29
Bh9L_N29hElMJ_-_1zqS0YsHkypVaAMLZdWLjXgfaWRZh89KWEV3sZzSn66rFF1ASwuTQEsR7rBJq
aCFAl5twUH9u07C5now_sUoKuYNhIT5bxyFSrqAf0ZA0G2ONr2uv74nOZMnBS0HFoRP0-seL78tGQ
_MgWgS7aW85STZ953VeNvjk6uatVS__fM0yYG8ENxZntG2zl7K8Bf8wE2ew1nx_nYao7YMkGQbOg4
iE99tAf2tHdtuPd19ivaQ1RS6MG8qDMMlMDDHTmN78uSp1jhMENwFcDlBLDIsFhcd0iB3Ip21j653
osGg0CjPA1Q_RLbfNkOu9AFiRyTWaPvvcuRvWT4",
	"signature":"0/1Ldval/TIPf2tTgTKUAxRy44VwEJ7ffsFLMWFcNoA=",
	"access_token":"00D50000000IZ3Z!AQ0AQDpEDKYsn7ioKug2aSmgCjgrPjG9eRLza8jXW
oW7uA90V39rvQaIy1FGxjFHN1ZtusBGljncdEi8eRiuit1QdQ1Z2KSV"
}

As explained later, the client app can decode the id token to obtain a claims set:

{
	"exp": 1396472567,
	"sub": "https://login.salesforce.com/id/00Dx0000000A9y0EAC/005x0000000UnYmAAK",
	"aud": "3MVG9lKcPoNINVBLWJnB_YrtPDE3WyOp1FkG4J6cGmiEmvPiwm4e1CbxRee8ETH5O82AYDiKqS3qogyJSxLsn",
	"iss": "https://login.salesforce.com",
	"iat": 1396472447
}

To guard against impersonation attacks, the client app must now verify that the ID token's aud parameter matches its own client id.

Now that the client app has the ID token, it knows that the issuer authenticated the end-user for the purpose of accessing that client app. The client app also has an access token to access resources at the authorization server, including the user profile service (known in the protocol specification as the UserInfo Endpoint).

User Profile Service

OpenID Connect defines a UserInfo Endpoint, which Salesforce implements as the User Profile Service. This service is similar to the Identity Service, but is preferred, since it follows established standards.

To obtain a user's profile, the app calls the User Profile Service at https://login.salesforce.com/services/oauth2/userinfo, supplying the access token received in step (6) of the protocol in the Authorization HTTP header in the usual manner:

Authorization: Bearer 00D...JeP

The user profile service will respond with a set of claims (attributes) concerning the user. (Spaces in the username and email addresses are artifacts of the wiki software - they are not in the data returned by the user profile service).

{
	"sub": "https://login.salesforce.com/id/00Dx0000000A9y0EAC/005x0000000UnYmAAK",
	"user_id": "005x0000000UnYmAAK",
	"organization_id": "00Dx0000000A9y0EAC",
	"preferred_username": "user@ example.com",
	"nickname": "user",
	"name": "Pat Patterson",
	"email": "user@ example.com",
	"email_verified": true,
	"given_name": "Pat",
	"family_name": "Patterson",
	"zoneinfo": "America/Los_Angeles",
	"photos": {
		"picture": "https://c.na1.content.force.com/profilephoto/005/F",
		"thumbnail": "https://c.na1.content.force.com/profilephoto/005/T"
	},
	"profile": "https://na1.salesforce.com/005x0000000UnYmAAK",
	"picture": "https://c.na1.content.force.com/profilephoto/005/F",
	"address": {
		"region": "ca",
		"country": "US"
	},
	"urls": {
		"enterprise": "https://na1.salesforce.com/services/Soap/c/30.0/00Dx0000000A9y0",
		"metadata": "https://na1.salesforce.com/services/Soap/m/30.0/00Dx0000000A9y0",
		"partner": "https://na1.salesforce.com/services/Soap/u/30.0/00Dx0000000A9y0",
		"rest": "https://na1.salesforce.com/services/data/v30.0/",
		"sobjects": "https://na1.salesforce.com/services/data/v30.0/sobjects/",
		"search": "https://na1.salesforce.com/services/data/v30.0/search/",
		"query": "https://na1.salesforce.com/services/data/v30.0/query/",
		"recent": "https://na1.salesforce.com/services/data/v30.0/recent/",
		"profile": "https://na1.salesforce.com/005x0000000UnYmAAK",
		"feeds": "https://na1.salesforce.com/services/data/v30.0/chatter/feeds",
		"groups": "https://na1.salesforce.com/services/data/v30.0/chatter/groups",
		"users": "https://na1.salesforce.com/services/data/v30.0/chatter/users",
		"feed_items": "https://na1.salesforce.com/services/data/v30.0/chatter/feed-items"
	},
	"active": true,
	"user_type": "STANDARD",
	"language": "en_US",
	"locale": "en_US",
	"utcOffset": -28800000,
	"updated_at": "2014-01-31T18:05:32.000+0000",
	"is_app_installed": true,
	"custom_attributes": {
		"title": "Developer Evangelist"
	}
}

Let's take a closer look at some of the more interesting fields in that response:

sub A unique identifier for the user, with the form https://login.salesforce.com/id/[ORG_ID]/[USER_ID]]
user_id The user's Salesforce.com ID.
organization_id The user's org ID.
preferred_username The user's login username.
name The user's name as displayed in the Salesforce.com user interface.
email The user's email address.
email_verified Indicates whether the organization has email verification enabled (true), or not (false).
photos URLs to access the user's picture and thumbnail.
urls URLs for a range of API endpoints.
custom_attributes Any custom attributes you defined as part of the connected app configuration.

Salesforce's user profile service is located at https://login.salesforce.com/services/oauth2/userinfo, but OpenID Connect includes a mechanism for apps to discover metadata such as the UserInfo Endpoint.

Discovery

OpenID Connect Discovery defines a mechanism for an OpenID Connect Relying Party to discover the End-User's OpenID Provider and obtain information needed to interact with it, including its OAuth 2.0 endpoint locations.

In Salesforce's implementation, given an issuer location, such as https://login.salesforce.com, the app appends /.well-known/openid-configuration and GETs the resulting URL, https://login.salesforce.com/.well-known/openid-configuration. The response is a JSON object containing OpenID Connect metadata according to the OpenID Connect Discovery specification:

{
    "issuer": "https://login.salesforce.com", 
    "authorization_endpoint": "https://login.salesforce.com/services/oauth2/authorize", 
    "token_endpoint": "https://login.salesforce.com/services/oauth2/token", 
    "userinfo_endpoint": "https://login.salesforce.com/services/oauth2/userinfo",
    "revocation_endpoint": "https://login.salesforce.com/services/oauth2/revoke", 
    "jwks_uri": "https://login.salesforce.com/id/keys", 
    "display_values_supported": [
        "page", 
        "popup", 
        "touch"
    ], 
    "id_token_signing_alg_values_supported": [
        "RS256"
    ], 
    "response_types_supported": [
        "code", 
        "token", 
        "token id_token"
    ], 
    "scopes_supported": [
        "id", 
        "api", 
        "web", 
        "full", 
        "chatter_api", 
        "visualforce", 
        "refresh_token", 
        "openid", 
        "profile", 
        "email", 
        "address", 
        "phone", 
        "offline_access"
    ], 
    "subject_types_supported": [
        "public"
    ]
}

In the general case, a relying party can obtain everything it needs to interact with an OpenID Provider via Discovery.

ID Tokens

The OpenID Connect ID token is a signed data structure that contains authenticated user attributes, encoded as a JSON Web Token (JWT). As well as identifying the end-user, or subject, it also identifies the token issuer and, crucially, audience, the client app to which the token was issued.

In the example above, if GoodApp were to use OpenID Connect instead of OAuth 2.0, it would use the ID token to represent user identity, rather than the access token. On receipt of an ID token, GoodApp validates the signature and decodes the payload, verifying that the token was issued by ProviderCo, to GoodApp. Now user resources are secure, since ID tokens issued to BadApp will identify BadApp as the audience, and fail validation at GoodApp. Let's take a closed look at the ID token format.

An ID tokens is encoded as a JWT, a string comprising three '.' separated components, each base64url encoded:

<base64url encoded header>.<base64url encoded payload>.<base64url encoded signature>

Decoding the header yields a JSON object specifying the cryptographic operation that were applied to the claims set to yield the signature, for example:

{
	"alg": "RS256",
	"typ": "JWT",
	"kid": "188"
}

The following header parameters, a subset of those defined in JSON Web Signature (JWS), are used in JWT tokens issued by Salesforce:

alg The cryptographic algorithm used to secure the JWS. Salesforce ID tokens use 'RS256', representing RSASSA-PKCS-v1_5 using SHA-256.
typ The MIME Media Type of the token. This is always 'JWT'.
kid An identifier indicating the key that was used to secure the token. A list of signing keys can be obtained from https://login.salesforce.com/id/keys.

Decoding the claims set yields another JSON object, containing 'claims', or attributes, concerning the user in question, as well as the issuer and audience for the claims:

{
	"exp": 1396472567,
	"sub": "https://login.salesforce.com/id/00Dx0000000A9y0EAC/005x0000000UnYmAAK",
	"aud": "3MVG9lKcPoNINVBLWJnB_YrtPDE3WyOp1FkG4J6cGmiEmvPiwm4e1CbxRee8ETH5O82AYDiKqS3qogyJSxLsn",
	"iss": "https://login.salesforce.com",
	"iat": 1396472447
}

The following claim parameters, a subset of those defined in JSON Web Token (JWT), are used in JWT tokens issued by Salesforce:

exp Identifies the expiration time on or after which the JWT must not be accepted, represented as the number of seconds since the Unix epoch (00:00:00 UTC on 1 January 1970).
sub Identifies the user that is the subject of the JWT. The sub</sub> parameter is a URL of the form <code>https://login.salesforce.com/id/<org id>/<user id>
nonce If the client passed a nonce in the token request, it is included in the response.
aud The client_id of the application that requested the token.
iss Identifies the issuer of the token. For production orgs, this will be https://login.salesforce.com.
iat The time at which the token was issued, represented as the number of seconds since the Unix epoch.

Decoding the signature yields the binary output of the signing process. The app needs to obtain the public key identified by the kid parameter to verify the signature.

Obtaining Salesforce ID Token Signing Keys

A list of signing keys can be obtained from https://login.salesforce.com/id/keys. It has the form:

{
	"keys": [
		{
			"kty": "RSA",
			"n": "AKZZvZ34MnQBGMej6QRalOB7QF4dwOwIK4EO9OFsS5lNQmyBw6gVueAmCl1BTY1vQ...U5mwBk0r3b",
			"e": "AQAB",
			"alg": "RS256",
			"use": "sig",
			"kid": "188"
		},
		...
	]
}

Each key object includes an RSA public key modulus (n) and exponent (e) represented according to JSON Web Key (JWK), as well as parameters indicating the key type (kty), algorithm (alg), etc.

A full discussion of ID token validation is outside the scope of this article. See JSON Web Token (JWT) and JSON Web Signature (JWS) for full details.

Summary

OpenID Connect provides identity services built on the existing OAuth 2.0 protocol. Salesforce Identity's OpenID Connect client and provider allow you to identity-enable your orgs and applications. This article provided an overview of OpenID Connect as implemented by Salesforce.

References

About the Author

Pat Patterson is a Developer Evangelist Architect at salesforce.com. Describing himself as an 'articulate techie', Pat hacks all manner of code from Node.js web apps down to Linux kernel drivers, writing it all up on the Force.com blog, his own blog Superpatterns, and tweeting along the way.