Nearly every web application prompts users to create an account and log in. In order to create an account, users are asked to provide their name, their email address, a password, and password confirmation. Not only does this take a lot of effort for the user (50+ keystrokes), but it also creates security concerns, as users often create the same password on multiple sites and some sites do not properly secure these credentials.
OpenID exists to enable federated identity, where users are able to authenticate with the same identity across multiple web applications. Both users and web applications trust identity providers, such as Google, Yahoo!, and Facebook, to store user profile information and authenticate users on behalf of the application. This eliminates the need for each web application to build its own custom authentication system, and it makes it much easier and faster for users to sign up and sign into sites around the Web.
OpenID Connect is the next-generation version of OpenID. The development of OpenID Connect has taken into account two key concepts:
Passing permission to access authentication information (the user’s identity) to a site is very similar to passing along delegated access to a user’s data (such as their calendar). Developers shouldn’t have to use entirely different protocols for these two different use cases—especially because many developers need to handle both in their applications.
The specification should be modular—enabling spec compliance without requiring implementation of automated discovery, associations, and other complex bits included in the previous versions of OpenID.
The basic flow for OpenID Connect is:
The application requests OAuth 2.0 authorization for one or more
of the OpenID Connect scopes (openid
,
profile
, email
, address
) by redirecting the user to an
identity provider.
After the user approves the OAuth authorization request, the
user’s web browser is redirected back to the application using a
traditional OAuth flow. The app makes a request to the Check ID
Endpoint. This endpoint returns the user’s identity (user_id
) as well as other bits, such as the
aud
and state
, which must be verified by the client to
ensure valid authentication.
If the client requires additional profile information about the user, such as the user’s full name, picture, and email address, the client can make requests to the UserInfo Endpoint.
Because OpenID Connect is built on top of OAuth 2.0 and is designed as a modular specification, it’s much easier for you to implement federated authentication for your website in a compliant way. Since this is a Getting Started book, this chapter will primarily discuss the OpenID Connect Basic Client implementation.
With OpenID Connect authentication, there is an additional type of
OAuth token: an ID token. The ID token, or id_token
, represents the identity of the user
being authenticated. This is a separate token from the access token, which
is used to retrieve the user’s profile information or other user data
requested during the same authorization flow.
The ID token is a JSON Web Token (JWT), which is a digitally signed and/or encrypted representation of the user’s identity asserted by the identity provider. Instead of using cryptographic operations to validate the JSON Web Token, it can be treated as an opaque string and passed to the Check ID Endpoint for interpretation (see below). This flexibility keeps with the spirit of OAuth 2.0 and OpenID Connect being significantly easier to use than their predecessors.
Although the end user flow is quite similar, the security precautions necessary for authentication are much different than those for authorization because of the potential for replay attacks. Replay attacks occur when legitimate credentials are sent multiple times for malicious purposes.
There are two main types of replay attacks we wish to prevent:
An attacker capturing a user’s OAuth credentials as they log in to a site and using them later on the same site.
A rogue application developer using the OAuth token a user was issued to log in to their malicious app in order to impersonate the user on a different legitimate app.
The OAuth 2.0 specification requires the OAuth endpoint and APIs to be accessed over SSL/TLS to prevent man-in-the-middle attacks, such as the first case.
Preventing rogue application developers from replaying legitimate OAuth credentials their app received in order to impersonate one of their users on another app requires a solution specific to OpenID Connect. This solution is the Check ID Endpoint. The Check ID Endpoint is used to verify that the credentials issued by the OAuth provider were issued to the correct application.
It is recommended that all developers use the Check ID Endpoint or decode the JSON Web Token to verify the asserted identity, though this is not strictly necessary in some cases when the application uses the server-side Web Application flow and the UserInfo Endpoint provides all required information.
The server-side Web Application flow, when implemented as per the specification, only issues an authorization code through the user’s web browser. The web application should not ever accept an access token or identity token directly from the browser. The access token and identity token are retrieved by exchanging the authorization code in a server-to-server request. Since this exchange requires the server-to-server call to be authenticated with the client ID and client secret of the app which the authorization code was issued for, the OAuth token service will naturally prevent an app from accidentally using an authorization code issued to another app.
Alternatively, the client-side Web Application flow issues an access token and identity token directly to the app through the browser using a hash fragment. The access token and identity token are often sent to the backend web server using JavaScript in order to authenticate the user. In this case, the web server must either cryptographically verify the ID Token or call the Check ID endpoint to verify it was issued to the correct application. This is called “verifying the audience” of the token. See Check ID Endpoint for more information.
The process of obtaining user authorization for OpenID Connect is nearly identical to the process of obtaining authorization for any OAuth 2.0 enabled API. You can use either the client-side implicit flow (as described in Chapter 3) or the server-side web app flow (as described in Chapter 2).
As with any usage of these flows, the client generates a URL pointing at the OAuth Authorization Endpoint and redirects the user to that URL. The following parameters are passed:
client_id
The value provided to you when you registered your application.
redirect_uri
The location the user should be returned to after they approve the authentication request.
scope
openid
for a basic OpenID
Connect request. If your client needs access to additional profile
information for the user, additional scopes can be profiled in this
space-delimited string: profile
,
email
, address
.
response_type
id_token
to indicate that
an id_token
is required for the
application. Additionally, a response type of token
or code
must be included, separating the two
response types by a space. token
indicates the client-side Web Application flow, while code
indicates the server-side Web
Application flow.
nonce
A unique value used by your application to protect against replay and cross-site request forgery (CSRF) attacks on your implementation. The value should be a random unique string for this particular request, unguessable and kept secret in the client (perhaps in a server-side session). This identical value will be included in the ID token response (see below).
The following is an example of a complete Authorization Endpoint URL, using the client-side implicit flow:
https://accounts.example.com/oauth2/auth? scope=openid+email& nonce=53f2495d7b435ac571& redirect_uri=https%3A%2F%2Foauth2demo.appspot.com%2Foauthcallback& response_type=id_token+token& client_id=753560681145-2ik2j3snsvbs80ijdi8.apps.googleusercontent.com
After the user approves the authentication request, they will be
redirected back to the redirect_uri
.
Since this request uses the implicit flow, the redirect will include an
access token that can be used with the UserInfo Endpoint to obtain profile
information about the user. Additionally, and specific to OpenID Connect,
the redirect will also include an id_token
, which can be sent to the Check ID
Endpoint to get the user’s identity.
Here’s an example redirect:
https://oauth2demo.appspot.com/oauthcallback# access_token=ya29.AHES6ZSzX token_type=Bearer& expires_in=3600& id_token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY...
The client then needs to parse the appropriate parameters from the hash fragment in the URL and call the Check ID Endpoint to validate the response.
The Check ID Endpoint exists to validate the id_token
returned along with the OAuth 2.0
access_token
by ensuring that it was
intended for the correct client and is used by the client to begin an
authenticated session. As described above, this check is required for the
implicit flow for client-side applications (described in Chapter 3). If this check isn’t done correctly, the client
becomes vulnerable to replay attacks.
Here’s an example Check ID endpoint request:
https://accounts.example.com/oauth2/tokeninfo? id_token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY...
And the response:
{ "iss" : "https://accounts.example.com", "user_id" : "113487456102835830811", "aud" : "753560681145-2ik2j3snsvbs80ijdi8.apps.googleusercontent.com", "exp" : 1311281970 "nonce" : 53f2495d7b435ac571 }
If the response is returned without a standard OAuth 2.0 error, the following checks need to be performed:
Verify the aud
value in the
response is identical to the client_id
used in the Authorization
request.
Verify that the nonce
value
in the response matches the value used in the Authorization
request.
If this verification is completed successfully, the user_id
is known to represent the unique
identifier for the authenticated user, within the scope of the issuer
(iss
). If storing the identifier in a
user database table and multiple identity providers are supported by your
application, it is recommended that both values be stored upon account
creation and queried upon each subsequent authentication request.
While the Check ID Endpoint will return a unique identifier for the user authenticating to your application, many applications require additional information, such as the user’s name, email address, profile photo, or birthdate. This profile information can be returned by the UserInfo Endpoint.
The UserInfo Endpoint is a standard OAuth-authorized REST API, with
JSON responses. As when accessing any other API using OAuth, the access_token
can be passed either as an Authorization
header or as a URL query
parameter.
Here’s an example UserInfo request:
GET /v1/userinfo HTTP/1.1 Host: accounts.example.com Authorization: Bearer ya29.AHES6ZSzX
With the response:
{ "user_id": "3191142839810811", "name": "Example User", "given_name": "Example", "family_name": "User", "email": "[email protected]", "verified": true, "profile": "http://profiles.example.com/user", "picture": "https://photos.profiles.example.com/user/photo.jpg", "gender": "female", "birthday": "1982-02-11", "locale": "en-US" }
OpenID Connect does not define any specific profile fields as required and does allow for additional profile fields to be included in the response.
The objective of the call to the Check ID Endpoint is to verify the
legitimacy of the id_token
. However,
this requires an additional HTTP request to the OpenID Connect identity
provider. This additional request can be avoided since the id_token
is returned as a signed JSON Web Token
(JWT) instead of as an opaque blob. The JWT includes the same information
that is typically returned by the Check ID Endpoint, but the value is also
cryptographically signed by the server in a way that can be validated by
the client.
This gives the client the option to verify the signature using the JWT (for best performance) or simply call the Check ID Endpoint if the client wants to avoid cryptography.
Since the OpenID Connect specification is still under active development, experimental implementations by identity providers still differ from the specification. Here are some example requests and responses using these experimental implementations.
Google’s OpenID Connect implementation (see Figure 7-1) uses the following Endpoints:
https://www.googleapis.com/oauth2/v1/tokeninfo
https://www.googleapis.com/oauth2/v1/userinfo
Google does not have the generic openid
scope, but it supports the following
main scopes for its OpenID Connect implementation:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
Here’s an example authorization URL for Google’s OpenID Connect implementation:
https://accounts.google.com/o/oauth2/auth? scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile& state=ABC123456& redirect_uri=https%3A%2F%2Foauthssodemo.appspot.com%2Foauthcallback& response_type=token%20id_token& client_id=8819981768.apps.googleusercontent.com
In this example, we’re specifying a response_type
of token id_token
, indicating that we’re looking
for both an ID token and a traditional OAuth 2.0 access token (via the
implicit flow). After the user approves the request by clicking “Allow
Access,” Google redirects back to the redirect_uri
and includes an id_token
and an access_token
in the hash fragment of the URL.
The id_token
is a JSON Web Token
(JWT) and contains the user’s ID. This ID token can be validated by
comparing a cryptographic signature or the Check ID Endpoint can be
called. For simplicity, we’ll show how to call the Check ID Endpoint.
Here’s an example request:
https://www.googleapis.com/oauth2/v1/tokeninfo? id_token=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY...
Here’s the response:
{ "issued_to" : "8819981768.apps.googleusercontent.com", "user_id" : "113487456102835830811", "audience" : "8819981768.apps.googleusercontent.com", "expires_in" : 3465 }
After the Check ID response is properly validated by ensuring it’s
been issued for the correct application (by comparing the value of the
issued_to
parameter to the app’s
client ID), the app may wish to obtain additional profile information
about the user. This information, such as the user’s name or email
address, can be obtained as a JSON response from the UserInfo Endpoint.
The OAuth access_token
must be sent
to authorize the request. Here’s an example request:
GET /oauth2/v1/userinfo HTTP/1.1 Host: www.googleapis.com Authorization: Bearer ya29.AHES6ZSzX
Here’s the response:
{ "id": "110634877589748180443", "email": "[email protected]", "verified_email": true, "name": "Ryan Boyd", "given_name": "Ryan", "family_name": "Boyd", "link": "http://profiles.google.com/110634877589748180443", "picture": "https://lh6.googleusercontent.com/-XC1Cwt4OgfY/AAAAAAAAAAI/AAAAAAAACR8/SU9W99JQFvc/photo.jpg", "gender": "male", "birthday": "0000-10-05", "locale": "en-US" }
You’ll notice that the response indicates my birth year as 0000. I’m not that old; Google uses this special value to indicate that the birth year is not shared.
Facebook’s implementation of identity using OAuth 2.0 isn’t documented as being OpenID Connect. However, it works similarly to the specification, with a few minor differences to account for in client code.
Facebook uses the following Endpoint:
https://graph.facebook.com/me
Facebook does not provide Check ID Endpoint functionality, and for this reason I recommend using only the Authorization Code flow for server-side applications (described in Chapter 2) and not the implicit flow for client-side applications. If you use the client-side Web Application flow, you’ll have no ability to verify the access token was intended for use by your application, and thus can leave your app vulnerable to replay attacks.
Here we can see an example authorization URL for Facebook’s OpenID Connect implementation:
https://www.facebook.com/dialog/oauth? client_id=202627763128396& redirect_uri=https%3A%2F%2Foauth2demo.appspot.com%2Foauthcallback& state=ABC123456
Since a scope
is not specified,
Facebook defaults to requesting authorization for public profile
information. Additional information can be requested by specifying
scope
values such as email,read_stream
. Notice that Facebook uses
comma-delimited scope values instead of space-delimited values as
defined by the latest OAuth 2.0 specification.
Since a response_type
is not
specified, Facebook defaults to the Authorization Code flow for
server-side web applications. If you wish to use the implicit flow for
client-side web applications, specify a response_type=token
, though this is not
recommended.
As is typical with the Authorization Code flow for server-side web
applications described in Chapter 2, the user’s
browser will be redirected to the application’s redirect_uri
after the user approves access.
The redirect URL will include an authorization code in the code
query parameter. The application then
needs to exchange the authorization code for an access token by making a
request to the Token Endpoint.
While the authorization code exchange typically uses a HTTP POST, Facebook also supports using a HTTP GET:
https://graph.facebook.com/oauth/access_token? client_id=202627763128396& redirect_uri=https%3A%2F%2Foauth2demo.appspot.com%2Foauthcallback& client_secret=YOUR_APP_SECRET&code=123456
Since we’re using the Authorization Code flow for server-side web applications, there is no need to do a Check ID request. This is because the Authorization Code flow requires the application’s credentials to be sent securely to the server when exchanging an authorization code for an access token, resulting in an automatic check that the authorization code was issued to the current client. However, the application must keep the access token confidential on the server and prevent trusting any access token directly sent by the user, or the application could be vulnerable to the same type of replay attack that the Check ID endpoint was designed to prevent.
At this point, the application can obtain profile information for
the user via the me
endpoint of the
Graph API. Here’s an example request:
https://graph.facebook.com/me? access_token=123456abc123456abc
Here’s the response:
{ "id":"545296355", "name":"Ryan Boyd", "first_name":"Ryan", "last_name":"Boyd", "link":"http://www.facebook.com/rboyd", "username":"rboyd", "hometown":{ "id":"114952118516947", "name":"San Francisco, California" }, "location":{ "id":"114952118516947", "name":"San Francisco, California" }, "gender":"male", "email":"ryanu0040ryguy.com", "timezone":-8, "locale":"en_US", "verified":true, "updated_time":"2011-06-03T18:37:40+0000" }
The protocol is likely to change after receiving feedback from both identity providers and relying parties. Information on the current Developer Preview can be found on the OpenID Foundation site, including the detailed specifications and mailing lists to follow development of the specifications.
3.22.41.235