Oauth_2.0 - OpenID Connect AuthenticationNearly 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:
1. 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. 2. 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. 3. 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. ID Token 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. Security Properties 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:
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” on page 54 for more information. Obtaining User Authorization 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 spacedelimited 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. Check ID Endpoint 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:
UserInfo Endpoint 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": "user@example.com", "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. Performance Improvements 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. Practical OpenID Connect 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. For Google Google’s OpenID Connect implementation (see Figure 7-1) uses the following Endpoints:
Check ID
https://www.googleapis.com/oauth2/v1/tokeninfo UserInfo 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 Profile 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": "ryan.boyd@gmail.com", "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. For Facebook 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: UserInfo 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 redi rect_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":"ryan\u0040ryguy.com", "timezone":-8, "locale":"en_US", "verified":true, "updated_time":"2011-06-03T18:37:40+0000" } OpenID Connect Evolution 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. |