Chapter 16. The OAuth 2.0 Authorization Framework
Delegata potestas non potest delegari.
The OAuth 2.0 Authorization Framework, defined by RFC 6749, is an evolution of the OAuth 1.0 protocol. At the time of writing, it is used by several popular Web APIs such as the Google APIs, Facebook, and GitHub. Its main usage scenario is delegated constrained authorization. As an example, consider the fictional scenario depicted in Figure 16-1.
In the figure, you can see that:
- storecode.example is a website for storing and managing code repositories, with an associated Web API.
- checkcode.example is a service for building and analyzing code, providing functionalities such as continuous integration, coding rules checking, error estimation, and test coverage.
- Alice uses the storecode.example site to store and manage her private code.
Alice wants to use the checkcode.example service to analyze the code she’s stored at storecode.example. The fact that storecode.example provides an API is an important enabler for this scenario, but a problem remains: how can Alice allow checkcode.example to access some of her private code repositories?
A solution to this problem would be for Alice to provide her storecode.example credentials (e.g., username and password) to checkcode.example, so that this service could access her private code. However, this solution has several disadvantages:
- With these credentials, the checkcode.example service can do any action allowed for Alice, including accessing all her code and also changing it. In other words, this solution grants the checkcode.example unconstrained authorization over storecode.example.
- A compromise of the checkcode.example service would reveal Alice’s password, allowing an attacker to have full access to her resources.
- The only way for Alice to revoke the checkcode.example access would be for her to change her credentials. As a side effect, all other applications that are authorized to access Alice’s code (e.g., www.hostcode.example, a hosting service) would also have their access revoked.
A better solution is for Alice to grant checkcode.example a constrained authorization that allows this service to perform only a subset of operations (e.g., read from the master branch of only one repository), for a delimited time period. Alice should also be able to revoke this authorization grant at any time, without disturbing other services that access her resources.
This previous example illustrates the inadequacy of the simple client-server model to express the authorization requirements of Web APIs. Namely, since a Web API is an interface for application consumption, the distinction between client applications and human users is an important feature of an authorization model. Following this, the OAuth 2.0 Framework introduces a model with four roles:
- Alice plays the resource owner role—the entity owning or able to grant authorizations to a protected resource. In the remainder of this chapter, we will refer to the resource owner as simply the user.
- storecode.example plays the resource server role—the entity that provides the interface for accessing the protected resources.
- checkcode.example plays the client role—the application accessing the protected resource on behalf of the resource owner.
- The authorization server is a fourth role, and is responsible for managing authorizations and access grants. Typically, this role is also played by the resource server. However, the OAuth 2.0 Framework allows for separate deployments of these roles.
One of the main aspects of this model is that user and client are not synonymous in this scenario. Instead, they are separate entities, each one having its own well-defined identity and most of the time belonging to different security boundaries. For instance, access to a protected resource is frequently associated with the resource owner (the user) and the application performing the access (the client) on its behalf. This is a distinctive feature of this model when compared to the simpler client-server scenarios addressed previously. However, despite the focus on delegated authorization and on the user-client-server model, the OAuth 2.0 Framework also supports simpler scenarios where the client is acting on its own behalf and there isn’t a user involved.
Client Applications
The OAuth 2.0 Framework aims to be usable with different kinds of client applications, such as:
This wide range of client types presents different challenges, particularly concerning the long-term storage of authentication secrets. To be able to participate in an OAuth 2.0 deployment, a client must be previously registered on the authorization server. In a typical OAuth 2.0 scenario, the client owner provides a set of information, such as:
- Descriptive information, intended for human consumption, such as the application name, logos, home page, or version information
- Technical information, used on the protocol steps, such as redirect URIs or required authorization scopes
On the other hand, the authorization server assigns a client_id
string to the client, uniquely identifying it.
Some clients may also receive a client_secret
string that allows them to authenticate to the authorization server during some protocol steps.
In this context, the OAuth 2.0 divides clients into two types:
-
Confidential clients can securely store the
client_secret
and use it in protocol steps. The typical example is classic server-side web applications, where the client credentials are stored on the server side. -
Public clients aren’t able to securely store credentials.
These clients don’t have
client_secret
, but they still have an assignedclient_id
. A typical example is client-side JavaScript applications, which are unable to securely store long-term secrets, since they are executed entirely in the user’s browser.
Clients are typically classified as confidential or public during registration, based on the input provided by the client owner.
At the time of this writing, the typical scenario is for the client owner to register via web forms, such as the one shown in Figure 16-2.
However, a specification to define the dynamic registration of clients, using a Web API, is also being developed by the OAuth IETF working group.
An authorization server can also associate authorization polices with a registered client, limiting its permissions. As an example, Figure 16-3 depicts the T.AS class model for representing clients, where we can see that a client is associated with:
- The OAuth 2.0 flow where it can participate
- The set of authorizations—named scopes, as we will see shortly—that can be delegated to it
Accessing Protected Resources
In the OAuth 2.0 Framework, a client’s access to a protected resource must include an access token, as illustrated in Figure 16-4. At the time of writing, the framework defines only bearer token usage,[10] meaning that the access token is simply added to the request message without any further binding. As we have seen before, bearer tokens are simpler to use but have several security drawbacks. In particular, they should always be used with transport security and the client must ensure that they are sent only to the associated resource server. Because of these limitations, the OAuth IETF group is also working on a specification for MAC-based access tokens: the use of the access token must be combined with the proof of possession of a cryptographic key, via the computation of a MAC (message authentication code).
The access_token
is a representation of an authorization grant and is used by the resource server to obtain information about the requester, namely to enforce the resource’s authorization policy.
This may sound rather vague, but we will return to this subject later in the chapter and provide concrete examples of access tokens and of the contained information.
We will also see how an ASP.NET-based resource server can extract a token from the request message and transform it into identity and authorization information.
The recommended way of binding a access token to a request message is by using the Authorization
header with the Bearer
scheme:
GET https://storecode.example/resource HTTP/1.1 Authorization: Bearer the.access.token
This recommended method follows the generic HTTP authentication framework, presented in Chapter 1.
However, it’s also possible to send the access token in an application/x-www-form-urlencoded
body or in the request URI query string, using the access_token
field, but it’s not recommended.
The use of access tokens in request URIs is particularly sensitive, since this information is typically logged and therefore can easily be leaked.
Obtaining Access Tokens
A client application obtains access tokens by requesting them from the token endpoint, which is part of the authorization server, as illustrated by Figure 16-5.[11] The token request includes an authorization grant, which is an abstract concept representing the information on which the authorization decision is based. This authorization grant can take multiple implementations.
In simple scenarios, where the client application is accessing the resource server on its own behalf (there is no user involved), the authorization grant can just be the client credentials.
In OAuth 2.0 terminology, this is called the client credentials grant flow—the authorization is completely based on the client credentials.
This option requires the client to be of the confidential type—that is, to have an assigned client_secret
.
If there is a user involved, this authorization grant can be based on the user’s password credentials, provided by the user to the client, as depicted in Figure 16-6.
This is called the resource owner password credentials grant flow in the OAuth 2.0 Framework. At first sight, the availability of this option in OAuth 2.0 may seem unreasonable, since one of its goals was to avoid this exact credentials disclosure. However, it can make sense in some scenarios, particularly when the user has a high trust level on the client application (e.g., enterprise scenarios). First, in OAuth 2.0, the password does not need to be persisted by the client application and used on every request. Instead, it is just used to request the access token and can be removed immediately after. So, if the user changes the password, the access token may remain valid. Another advantage of this authorization grant type is that it is simpler to implement than the alternatives, especially when the client is a native mobile application.
Another option is the use of an authorization code that represents a delegated authorization performed by the user without revealing her password. This option is called authorization code grant flow and will be described in detail in the next section.
The token request is handled via a POST
to the token endpoint URI with an application/x-www-form-urlencoded
body containing the authorization grant type and values.
For instance, on the authorization code grant flow, the grant type is authorization_code
and the grant value is the authorization code:
POST https://authzserver.example/token_endpoint HTTP/1.1 Content-Type: application/x-www-form-urlencoded Host: authzserver.example grant_type=authorization_code& code=the.authorization.code
If the client is confidential (the client has an assigned client_secret
), this token request must also include client authentication information.
The OAuth 2.0 Framework defines two alternatives for this:
-
Using the
Basic
HTTP authentication scheme, where theclient_id
and theclient_secret
are used as the username and password, respectively -
Inserting the
client_id
andclient_secret
as fields of the token request body
If the request is successful, the response contains a application/json
body including the access token value, its type (e.g., bearer
), and validity:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Cache-Control: private, max-age=0, must-revalidate {"access_token":"the.access.token","token_type":"bearer", "expires_in":3600, ... other info ...}
Authorization Code Grant
The authorization code grant provides a way for a user to delegate a constrained authorization to a client application, without revealing his credentials to the client. Instead, the user interacts directly with the authorization endpoint of the authorization server via a user agent (e.g., a web browser or web view). This flow starts with the client application redirecting the user agent to this authorization endpoint, as shown in Figure 16-7. The client uses the query string of the authorization endpoint request URI to embed a set of authorization request parameters:
https://authzserver.example/authorization_endpoint? client_id=the.client.id& scope=user+repo& state=crCMc3d0acGdDiNnXJigpQ%3d%3d& response_type=code& redirect_uri=https%3a%2f%2fclient.example%2fcallback&
For instance, the response_type
parameter defines the authorization grant that is being requested, since the authorization endpoint can be used with a different flow, while the scope
parameter characterizes the authorization that is being requested.
After receiving this request, the authorization server starts an out-of-protocol user interaction with the aim of authenticating the user and optionally requesting her consent for the client requested authorization.
The user authentication protocol is not defined by the OAuth 2.0 protocol, leaving the authorization server free to choose the most appropriate one, ranging from a simple form-based username and password scheme to a distributed federation protocol.
After a successful authentication, the authorization server can also ask the user if she consents to the authorization requested by the client application.
Here, the authorization server uses the client’s descriptive information (e.g., application name, logo, and home URI), defined during the registration process, to better inform the user.
Finally, the authorization server redirects the user to the client application, using the value of the redirect_uri
request parameter, with an authorization code embedded in the request URI, as depicted in Figure 16-9 and in the following URI:
https://client.example/callback? code=52...e4& state=cr...3D
For security reasons, the set of redirect URIs used by a client should be preconfigured. T.AS does exactly that, as shown earlier in Figure 16-3, where each client is related to a collection of redirect URIs.
A final flow of the OAuth 2.0 Framework, named implicit grant, returns the access token immediately in the authorization response from the authorization endpoint. This is the only OAuth 2.0 flow where the access token is not returned from the token endpoint.
Scope
As stated before, one of the goals of OAuth 2.0 is to allow clients to access resources, under a constrained authorization, eventually delegated by a user. For this, the framework uses the concept of scope as a way to define these authorization constraints. Formally, a scope is a list of space-delimited identifiers, where each one defines a type of authorization. For instance, the following is a list of some scope identifiers used by the GitHub Web API:
-
user
authorizes the client to read/write to the user’s profile information. -
user:email
authorizes the client to read the user’s email. -
public_repo
authorizes the client to read/write to the user’s public repositories.
This means that the string user:email public_repo
defines a scope with the read email authorization and the read/write repositories authorization.
Typically, these scope identifiers and associated semantics are defined by the resource servers. They typically also have an associated human-readable description, used when presenting users with authorization consent forms.
As an example, in T.AS a scope is modeled by the following fields, as shown in Figure 16-3:
- The scope identifier
- The scope display name and description, used for human interface purposes, such as when requesting the user’s authorization consent
- The list of clients allowed to request this scope
The set of scopes available for a client may be restricted, as also shown in Figure 16-3.
Scopes are intensively used by the protocol, typically via a scope
parameter.
When using the client credentials or resource owner password grants, the client can include the scope
parameter in the token request sent to the token endpoint, as a way to define the request authorization.
Similarly, when using the authorization code or implicit grant flows, the client can include the scope
parameter in the authorization request sent to the authorization endpoint.
The authorization server is free to concede an authorization scope that is different from the one requested, namely based on the user’s consent. Accordingly, the scope
parameter is also present on the token response to inform the client of the granted authorizations.
Front Channel Versus Back Channel
To better understand the OAuth 2.0 Framework, it is important to realize that the client communicates with the authorization server in two distinct ways: via the back channel and via the front channel.
The back channel is the direct communication between the client and the token endpoint, such as the one depicted in Figure 16-5, whereas the front channel is the indirect communication between the client and the authorization endpoint via the user’s agent and based on HTTP redirects (Figure 16-7).
Therefore, the front channel has some significant limitations.
Since it’s based on redirects, it imposes restrictions on the HTTP features that can be used.
The request method must be a GET
, and the request information must be passed in the request URI, namely in the URI’s query string:
https://authz_server.example/authorization_endpoint? client_id=the.client.id& scope=user+repo& state=crCMc3d0acGdDiNnXJigpQ%3d%3d& response_type=code& redirect_uri=https%3a%2f%2fclient.example%2fcallback&
Also, the response must always be a redirect, so the response information is also passed in the redirected request URI:
https://client.example/callback? code=52...e4& state=cr...3D
In case of error, the standard HTTP status codes cannot be used (the response must always be a redirect).
Instead, the error
and error_description
parameters are used to convey this information on the URI’s query string:
https://client.example/callback? error=access_denied& error_description=authorization+not+granted
Also, because the front channel runs via the user agent, it is not safe for the client credentials (client_secret
) transmission, since then they would be visible to the user.
Thus, in all front-channel requests the client is identified (the client_id
is sent) but not authenticated.
Since the client_id
is public, it is very easy for an attacker to forge valid authorization requests.
Finally, the front channel also does not ensure any correlation between the requests from the client to the authorization server and the corresponding responses. It is subject to CSRF (cross-site request forgery) attacks, where a third-party malicious site instructs the user’s browser to make a request back to the client, thereby simulating an OAuth 2.0 front-channel response. Using this technique, the attacking site can control the access token that will be used by the client—for instance, by using an authorization code issued to the attacker account.
To address this, the OAuth 2.0 Framework uses a state parameter, present in both the request and the response, to ensure this correlation. The client creates a random state value and includes it in the request via the front channel. At the end, the authorization server includes the same value in the response returned via the front channel. This way, the client has a mechanism to correlate a received response with a previous request. RFC 6819—OAuth 2.0 Threat Model and Security Considerations—provides more information on how to adequately use this protection mechanism.
With all these restrictions and problems, you may wonder why the front channel is used at all. The main reason is that it allows for the authorization server to directly interact with the user, without any client intervention or visibility. Figure 16-8 shows how this feature can be used for the authorization server to authenticate the user and ask for her consent.
On the other hand, since the back channel connects the client directly to the token endpoint, the protocol is not limited to HTTP redirects.
For instance, the token request is a POST
HTTP request where the parameters are represented in the body and the response may use HTTP status codes to represent different error conditions (e.g., 400
for a bad request or 401
for invalid client authentication).
If the client possesses a client_secret
(confidential client), it must use it in the back channel to authenticate itself.
For that purpose, the OAuth 2.0 Framework recommends using the HTTP basic scheme, where the username and password are replaced by the client_id
and client_secret
, respectively:
POST https://authz_server.example/token_endpoint HTTP/1.1 Content-Type: application/x-www-form-urlencoded Authorization: Basic dGhlLmNsaWVudC5pZDp0aGUuY2xpZW50LnNlY3JldA== Host: authz_server.example grant_type=authorization_code& code=the.authorization.code
Refresh Tokens
Because bearer access tokens are very sensitive, their usage lifetime should be limited. To address that, the OAuth 2.0 Framework includes refresh tokens, which can be used to obtain new access tokens. When an authorization grant is exchanged for the access token at the token endpoint, the response may also include a refresh token:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Cache-Control: private, max-age=0, must-revalidate {"access_token":"the.access.token","token_type":"bearer", "expires_in":3600, "refresh_token":"the.refresh.token"}
The client application can use this refresh token to obtain a new access token—for instance, when the older one is about to expire. This is also done by using the token endpoint, with the refresh_token
value on the grant_type
field:
POST https://authzserver.example/token_endpoint HTTP/1.1 Content-Type: application/x-www-form-urlencoded Host: authzserver.example grant_type=refresh_token& refresh_token=the.refresh.token
A successful response will include the new access token and associated lifetime. It can also contain a new refresh token.
The use of refresh tokens imposes additional requirements on a client application, which will have to securely store this new information, monitor the access token lifetime, and refresh it periodically. However, refresh tokens have some useful properties. From a security viewpoint, reducing the access token lifetime limits the consequences of malicious access to this information.
From an implementation and optimization viewpoint, the use of refresh tokens allows hybrid approaches wherein refresh tokens are revocable artifact tokens, pointing to entries on a repository, while access tokens are short-lived and nonrevocable stateful assertions. This makes access token verification more easily scalable, since no repository access is required. The fact that they are nonrevocable is compensated for by their reduced lifetime. On the other hand, you can easily revoke refresh tokens by simply removing or disabling the associated entry in the repository.
Resource Server and Authorization Server
The OAuth 2.0 Framework explicitly identifies two server-side responsibilities:
- The resource server exposes the HTTP interface to the protected resources and is a consumer of access tokens.
- The authorization server is responsible, among other things, for issuing the access tokens that are used in the protected resource access.
This does not mean that the resource server and the authorization server must be two independent entities. It is perfectly acceptable for these two roles to be implemented by the same physical servers and software component. However, the framework does allow decoupled architectures in which the authorization server is run by a separate software component (e.g., Thinktecture Authorization Server). It is even possible for a resource server to rely on an external authorization server run by another entity (e.g., Windows Azure Active Directory).
Despite allowing for these decoupled architectures, the OAuth 2.0 Framework does not specify how separate resource servers and authorization servers can cooperate. Aspects such as access token format and validation procedures are left completely open by the OAuth 2.0 Framework and must be defined for each scenario. Note that from a user or client viewpoint, this does not have any impact, since the access tokens are meant to be opaque to these parties.
Also, the information conveyed by the authorization server to the resource server, via the access token, is left undefined by the OAuth 2.0 Framework. The immediate option is for this to be a representation of the token request and associated grant, including:
- The resource owner identity, when the client is not accessing on its own behalf
-
The requesting client, identified by its
client_id
-
The requested authorization scope, identified by a
scope
string
The information associated with an access token should also include its temporal validity (tokens are not valid forever) and the token audience; that is, some identification of the resource server for whom the token was issued.
As an example, T.AS uses the JWT format to represent access tokens. Example 16-1 shows the payload of such a JWT token, containing:
-
The
sub
claim, with the user unique identifier, as well as therole
claim containing additional user’s claims -
The
client_id
claim, with the client application identity -
The
scope
claim, with the granted authorization scope
The token payload also contains the issuer identity (iss
claim) and the token’s intended destination or audience (aud
claim).
{ "exp": 1379284015, "aud": "http://resourceserver.example", "iss": "http://authzserver.example", "role": [ "fictional_character", "student" ], "client_id": "client2", "scope": [ "scope1", "scope2" ], "nbf": 1379280415, "sub": "Alice" }
As illustrated by Example 16-1 with the inclusion of the role
claim, the user’s identity can be more than just a simple identifier, which fits nicely into the claims model presented in The Claims Model.
Processing Access Tokens in ASP.NET Web API
As noted in Katana Authentication Middleware, the Katana project includes a set of authentication middleware classes.
One of these classes is the OAuthBearerAuthenticationMiddleware
, which implements the OAuth 2.0 Bearer
scheme.
The OAuthBearerAuthenticationMiddleware
behavior is configured via the OAuthBearerAuthenticationOptions
class, depicted in Figure 16-10.
When a request is received, the associated authentication handler performs the following steps:
-
Token extraction. If the request contains an
Authorization
header with theBearer
scheme, then its value is used as the access token. Otherwise, the authentication method returns without any identity. -
Authentication ticket extraction. After the token is obtained from the message, the
Options.AccessTokenFormat.Unprotect
method is used to extract an authentication ticket from the access token. As we saw before, this ticket contains both a claims-based identity and additional authentication properties. - Authentication ticket validation. Finally, the ticket’s validity period is checked.
At the end, if all the steps were performed successfully, the request’s access token is converted into a returned identity.
Using both the Options.Provider
and the Options.AccessTokenProvider
, you can customize the previous steps.
For instance, if defined, the Options.Provider
can be used to retrieve the access token from other message locations and also to validate or modify the extracted identity.
By default, Katana uses a custom access token format, coupled to its own authorization server.
However, by changing the Options.AccessTokenFormat
you can also configure Katana to accept JWT-based access tokens.
The Katana’s UseJwtBearerAuthentication
extension method does exactly this:
-
Receives a
JwtBearerAuthenticationOptions
with information on how to validate JWT tokens, including the allowed audiences and the signature validation information Internally creates an
OAuthBearerAuthenticationOptions
configured with aJwtFormat
, which is anISecureDataFormat
that uses the JWT format-
Registers an
OAuthBearerAuthenticationMiddleware
using these options
-
Registers an
Figure 16-10 illustrates the classes involved in this process. Taking advantage of this, Example 16-2 shows the configuration of a Katana-based resource server:
-
The
AllowedAudiences
property is configured with the resource server’s URI. -
The
IssuerSecurityTokenProviders
property is configured with the authorization server symmetric signature key.
config
.
Filters
.
Add
(
new
HostAuthenticationFilter
(
"Bearer"
));
app
.
UseJwtBearerAuthentication
(
new
JwtBearerAuthenticationOptions
{
AllowedAudiences
=
new
[]
{
"http://resourceserver.example"
},
IssuerSecurityTokenProviders
=
new
[]
{
new
SymmetricKeyIssuerSecurityTokenProvider
(
"http://authzserver.example"
,
"the.authorization.symmetric.signature.key"
)
},
Realm
=
"resourceserver.example"
,
AuthenticationMode
=
AuthenticationMode
.
Passive
});
OAuth 2.0 and Authentication
As indicated in the name of RFC 6749, the primary focus of OAuth 2.0 is authorization, not authentication. Its main goal is to enable client applications to access a subset of resources exposed by a Web API, on its own behalf or on an user’s behalf. However, implementations of this framework can also provide some forms of authentication.
As we stated at the beginning of this chapter, a request made by a client application to a resource server contains an access token. The primary aim of this token is to prove to the resource server that its bearer (i.e., the requesting client application) is authorized by the user to access a protected resource. A common way to achieve this is by having the access token:
- Authenticate the sending client application
- Authenticate the authorizing user
- Define the authorization scope
Figure 16-11 illustrates this authentication scenario, where the authorization server is the identity provider, the resource server is the relying party, and both the client and the user are the identity subjects.
For instance, the JWT token issued by T.AS and presented in Example 16-1 contains exactly these three pieces of information: the client_id
, the user’s claims (role
and sub
), and the authorized scope.
Note also that, when you’re using the Katana middleware, the access token information is transformed into an identity and propagated that way to the upper layers.
However, as we’ve seen before, the OAuth 2.0 Framework does not define the access token format and information, leaving that as an implementation-dependent aspect.
An OAuth 2.0 deployment where the access token contains only the authorized resources and HTTP methods, without any information about the client or the user, is perfectly conceivable.
So, most of the times an access token is also an authentication token, authenticating both the client and user to the resource server, but this depends on the concrete implementation of the framework.
The OAuth 2.0 Framework can also be used as the basis for a different kind of authentication: authenticating the user to the client application.
The idea is to use context-specific resources, which we will call user info resources, that expose the user’s identity as a way for the client to authenticate the user.
Consider, for instance, the GitHub API v3: a successful GET
on the https://api.github.com/user protected resource will return a representation with the email and the name of the user on whose behalf the used access token was issued.
This allows the client application to obtain the user’s identity, asserted by the resource server and authorization server, as illustrated in Figure 16-12.
At the time of this writing, it is rather common for web applications to use this OAuth 2.0-based technique to authenticate their users against social identity providers such as Facebook or GitHub. However, there are two shortcomings to this approach. First, it depends on context-specific user info resources, which aren’t defined by the OAuth 2.0 Framework. This implies that customization must be done for each different resource server. Secondly, and most important, this authentication usage is not secure for all of the OAuth 2.0 Framework flows—namely, the implicit flow and authorization code flow with public clients. For instance, in some flows, it is possible for a client application to use the authorization code or token to authenticate itself as the user on another client application. Consider, for instance, the implicit flow, where the token is delivered directly from the authorization endpoint to the client application via the user agent. After receiving this token, a malicious client can now present itself as the user to a different client and provide it with the same token. When the second client accesses the user info resource, it will receive the identity of the original user.
The OpenID Connect specification aims to solve these two problems by providing an identity layer on top of the OAuth 2.0 Framework.[12] First, it standardizes the concept of the user info resource (called UserInfo Endpoint in the specification) and the representation returned by it: a JSON object whose members are claims whose meaning is also defined by OpenID Connect. Example 16-3 shows the claims for our fictional Alice, returned by the Google UserInfo resource located at https://www.googleapis.com/oauth2/v3/userinfo.
{ "sub": "104107606523710296052", "email": "alice4demos@gmail.com", "email_verified": true }
OpenID Connect also extends the OAuth 2.0 token response definition, adding an id_token
field, as shown in Example 16-4.
The value of this field is a signed JWT token containing the user claims intended to be consumed by the client.
Notice that this contrasts with the access token, which is opaque to the client.
Example 16-5 shows the payload of a ID token containing identity claims about the user (the email
claim) but also the intended audience of the token: the aud
claims, whose value is the client_id
of the relying client application.
This aud
field binds an ID token to a consumer, preventing a malicious client from reusing the token on another client.
{ "access_token" : "ya..8s", "token_type" : "Bearer", "expires_in" : 3599, "id_token" : "ey..OQ" }
{ "sub": "104107606523710296052", "iss": "accounts.google.com", "email_verified": "true", "at_hash": "G_...hQ", "exp": 1380480238, "azp": "55...ve.apps.googleusercontent.com", "iat": 1380476338, "email": "alice4demos@gmail.com", "aud": "55...ve.apps.googleusercontent.com" }
By adding an identity layer upon OAuth 2.0, OpenID Connect provides an unified protocol for a client application to:
- Authenticate its users by obtaining a signed set of identity claims
- Obtain an access token that allows the client to access protected resources on the user’s behalf
Notice that classical identity federation protocols, such as SAML, WS-Federation, or the classical OpenID protocol, provide only the first feature. On the other hand, the OAuth 2.0 Framework provides only the second.
Scope-Based Authorization
When using the OAuth 2.0 Framework, you can remove the authorization decision from the resource server and externalize it to the authorization server. As we’ve seen before, a scope is associated with an access token to define exactly what is being authorized to the client. So, in such a scenario, the resource server’s task is simply to enforce the authorization decision expressed in the scope.
The Thinktecture.IdentityModel.45
library that we saw in Chapter 15 also provides an authorization attribute with this purpose, named ScopeAttribute
.
This attribute receives an array of scope identifiers in its constructor and authorizes the request only if the associated claims principal has scope claims matching each one of these identifiers.
Example 16-6 illustrates the ScopeAttribute
: POST
requests require an access token with the create
scope identifier, while DELETE
requests require an access token containing the delete
identifier.
public
class
ScopeExampleResourceController
:
ApiController
{
public
HttpResponseMessage
Get
()
{
return
new
HttpResponseMessage
{
Content
=
new
StringContent
(
"resource representation"
)
};
}
[Scope("create")]
public
HttpResponseMessage
Post
()
{
return
new
HttpResponseMessage
()
{
Content
=
new
StringContent
(
"result representation"
)
};
}
[Scope("delete")]
public
HttpResponseMessage
Delete
(
string
id
)
{
return
new
HttpResponseMessage
(
HttpStatusCode
.
NoContent
);
}
}
Conclusion
In this chapter you were introduced to the OAuth 2.0 Authorization Framework, its protocols, and its patterns. There are several features of this framework that we want to highlight. First, it introduces a model where there is a clear distinction between users, clients, and resource servers. This separation is particularly important when these three actors belong to distinct trust boundaries and will have a profound impact on the way we model security in web-based systems. OAuth 2.0 also introduces the authorization server as the entity issuing and managing access tokens used by clients to access resources, namely on the user’s behalf.
As we write, most of the OAuth 2.0 deployments use authorization servers that are coupled to the resource servers that use them. However, projects such as Thinktecture’s Authorization Server and Windows Azure Active Directory are starting to provide authorization servers that can be used in multiple contexts and with different resource servers. The OAuth 2.0 Framework also provides concrete patterns and protocols for different scenarios, ranging from clients accessing resources on their own behalf (the client credentials grant flow) to constrained authorization delegation via front-channel interaction (the authorization code grant flow).
OAuth 2.0 is also the foundation of the new OpenID Connect protocol, which provides decentralized authentication capabilities. The overall result is an integrated way to solve some of the authentication and authorization challenges on Web APIs.
Despite its significant current adoption, there are many critics of the OAuth 2.0 Framework. First, it hinders interoperability by providing so many protocol options and alternatives. This flexibility also has a negative security impact: by leaving so many options available, it increases the probability of insecure implementations. The use of bearer tokens[13] is another relevant critique of OAuth 2.0, namely due to all the problems that we described in Chapter 15.
However, even with these problems, the OAuth 2.0 Framework is an important part of the current Web API security landscape.
[10] See RFC 6750.
[11] The implicit flow is an exception to this rule; the token is not obtained from the token endpoint.
[12] Despite its name, OpenID Connect is much more similar to OAuth 2.0 than to the classic OpenID protocol.
[13] Bearer tokens are the only complete specification as we write this chapter; the MAC specification is still a work in progress.
Get Designing Evolvable Web APIs with ASP.NET now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.