ACS v2 OAuth 2.0 Delegation Support Explained
ACS v2's support of OAuth 2.0 (hereafter referred as "OAuth2") delegation authorization scenarios has been blogged and illustrated by a code sample (msdn article, source code). However, the mechanisms to develop the necessary integration points from various OAuth2's roles (e.g. Client, Authorization Server, and Resource Server etc) to ACS haven't been extensively described and I want to use today's blog to fill this gap.
Definition of OAuth2 Delegation
The OAuth2 protocol that ACS v2 conforms and implements is The OAuth 2.0 Authorization Protocol, draft-ietf-oauth-v2-13 (hereafter referred as "draft 13"). However, comparing to its heavy usage in ACS v2 documentations, "delegation" is not considered as a standard term by OAuth2 protocol specs (appears only once in "Introduction" section). To clarify, in most cases, ACS v2 OAuth2 delegation refers to the authorization flow and scenarios using the "authorization code" authorization grant (section 1.4.1 and 4.1 in draft 13).
The Parsley.com-access-Acme bank-on-behalf-of-Mary Example
"OAuth2.0 - Part 1-4" is one of my favorite screen-cast series which nicely explain how OAuth2 works in 10 min. The screencasts are around a story that Parsley.com, an online personal financial organizer, to access Acme.com, an online bank, for bank account data on behalf of Mary, who is the owner of these banking accounts at Acme.com (hereafter referred as the P.A.M example). I will borrow this as the showcase example to explain ACSv2's OAuth2 delegation support.
Delegation Deep Dive
First, let's sort out how various OAuth2 roles are mapped in the P.A.M example. The following table describes the mappings.
OAuth2 draft 13 | the P.A.M Example |
Resource Owner | Mary |
Protected Resource | Mary's account information with Acme.com |
Resource Server | Acme.com/accounts |
Client | Parsley.com |
Authorization Server | Acme.com/authserver (use the same server as the resource server) |
Resource Owner's Authentication credential with Client | Mary@gmail.com |
Resource Owner's Authentication Credential with Authorization Server | Mary@hotmail.com |
Now, let's break down the delegation process into flows and steps, examine the interactions between involved roles in greater details and highlight the ACS integration tasks.
In draft 13's section 4.1, there is an "Authorization Code Flow" diagram illustrating the major authorization flow steps. I took it and expanded to the following set of flow diagrams, which are tailored to the P.A.M example and capture several important flows missed in the original diagram, including the steps for the client to obtain its client credentials, the steps for the client to request protected resources from the resource server and the steps for the client to renew the access token.
Client Registration Flow
The client registration flow illustrated in above diagram includes the following steps:
(A) The administrator of Parsley.com (the client) registers Parsley.com with Acme.com/register/ (the application registration portal of Acme.com who runs the authorization server and resource server) by providing information about the client as well as a redirection URI.
Parsley.com's admin must provide a redirection URI for Acme.com's authorization server to redirect the user agent back in the "authorization code & access token flow" (described next) once authorization is obtained. The redirect URI conforms the requirements in draft 13 section 2.1.1.
ACS Integration Points: none.
(B) Assuming Acme.com/register/ approves the request of Parsley.com to be a registered client, it responds back with client credentials (a pair of client identifier and client secret) to Parsley.com.
As you may find out later in the next "Authorization Code & Access Token Flow" section, Acme.com's authorization server offloads the OAuth2 delegation protocol implementation and processing to ACS. With ACS, Acme.com has created a service namespace (say "acmebank") and a relaying party (hereafter referred as "RP") application representing its account service servicing the protected resources on the resource server. Acme.com/register/ uses ACS to handle Parsley.com's client registration as well, by calling ACS's service identity management service APIs. Internally, ACS records Parsley.com's registration and its credentials in a service identity instance. Upon receipt of the client credentials from ACS, Acme.com/register/ makes this data available to Parsley.com's admin.
ACS Integration Points:
#1: Create a service identity in ACS as the client credentials representation
The service identity can be created via the ACS management portal at https://acmebank.accesscontrol.windows.net/v2/mgmt/web/ServiceIdentity as the following. The client credentials' client identifier and client secret are represented by the Name and Password values respectively.
Alternatively, Acme.com/register/ can call ACS management service OData APIs to programmatically create a service identity. The following is the code snippet using the Common.ACS.Management.ManageService APIs provided in ACS 2.0 Samples at codeplex.
//Create a service identity
ManagementService svc = ManagementServiceHelper.CreateManagementServiceClient();
ServiceIdentity sid = new ServiceIdentity()
{
Name = "Parsley.com"
};
ServiceIdentityKey key = new ServiceIdentityKey()
{
Type = "Password",
Usage = "the client secret",
Value = "xxxxxx",
DisplayName = "the client secret"
};
svc.AddToServiceIdentities(sid);
svc.AddRelatedObject(
sid,
"ServiceIdentityKeys",
key);
svc.SaveChanges();
#2: Configure created Service Identity's RedirectAddress property
The value of the service identity's RedirectAddress property is mandatory for enabling OAuth2 delegation scenarios and the client’s redirect URI parameter must exactly match this value.
Currently, there is no way to view, edit and update the RedirectAddress property of a service identity through ACS management portal and these tasks have to be conducted programmatically through ACS management service OData APIs.
If the service identity is created through the portal in integration point #1, you may use code similar to the following to configure the RedirectAddress property.
//Update a service identity's RedirectAddress
ManagementService svc = ManagementServiceHelper.CreateManagementServiceClient();
ServiceIdentity serviceIdentity = svc.ServiceIdentities.Where(si => si.Name == name).FirstOrDefault();
if (serviceIdentity == null)
{
// TODO: throw exception
}
serviceIdentity.RedirectAddress = redirectAddress;
svc.UpdateObject(serviceIdentity);
svc.SaveChanges();
If in #1 you already create the service identity programmatically, you can simply initialize the RedirectAddress property in the service identity's constructor.
Authorization Code & Access Token Flow
The authorization code & access token flow illustrated in above diagram includes the following steps:
(C) Parsley.com (the client), initiates the flow by directing Mary (the resource owner)'s Internet Explorer (user-agent) to Acme.com/authserver/auth (the authorization server's authorization endpoint).
Mary logs into Parsley.com using credentials Mary@gmail.com and chooses to add her Acme.com accounts on the dashboard page. She is redirected to Acme.com authorization server's authorization page to log in with her credentials with Acme.com, Mary@hotmail.com. The redirect request also includes Parsley.com's client identifier, requested scope, local state, and a redirection URI to which Acme.com's authorization server will send the Internet Explorer back once access is granted (or denied) by Mary.
ACS Integration Points: none.
(D) Acme.com/authserver/ authenticates Mary (via the Internet Explorer) and establishes whether Mary grants or denies Parsley.com's access request.
After logging in, Acme.com/authserver/ shows Mary an UI for her to approve or deny Parsley.com's access to her account data with Acme.com.
ACS Integration Points: none.
(E) Assuming Mary grants access, Acme.com/authserver/ redirects the Internet Explorer back to Parsley.com using the redirection URI provided in step (C). The redirection URI includes an authorization code.
If Mary has granted the access, Acme.com's authorization server will make a request to ACS for a new authorization code by providing Parsley.com's client identifier, the name of RP for the Acme.com's account service, and Mary's credentials with the authorization server in the request. Upon receipt of this request, ACS will create a Delegation instance internally to represent the tri-party delegation relationship between Parsley.com, Acme.com/accounts/ and Mary. This delegation object comes with an AuthorizationCode property, which is populated with a Base64 encoded, random sequence of 16 bytes. ACS returns this code to the authorization server's authorization endpoint and the latter redirects to the redirection URI passed by Parsley.com in step (C) and append the code to the query parameter.
ACS Integration Points:
#3: Create a delegation and return the authorization code
The following code snippets show how to create the delegation representing the {service identity name or client name, RP name, user name} triplet and returns the authorization code value of the created delegation object via ACS management service OData APIs.
// Create a delegation and return the authorization code
public static string GetAuthorizationCode(string serviceIdentityName, string relyingPartyName, string userName, string identityProvider)
{
ServiceIdentity serviceIdentity;
RelyingParty relyingParty;
Delegation delegation;
string code = null;
try
{
serviceIdentity = GetServiceIdentityByName(serviceIdentityName);
relyingParty = GetRelyingPartyByName(relyingPartyName);
if (serviceIdentity != null && relyingParty != null)
{
delegation = CreateDelegation(serviceIdentity.Id, relyingParty.Id, userName, identityProvider);
if (delegation != null)
{
code = delegation.AuthorizationCode;
}
}
}
catch (Exception)
{
// delegation failed
code = null;
}
return code;
}
public static ServiceIdentity GetServiceIdentityByName(string name)
{
ManagementService svc = ManagementServiceHelper.CreateManagementServiceClient();
ServiceIdentity identity = svc.ServiceIdentities.AddQueryOption("$filter", "Name eq '" + name + "'").FirstOrDefault();
return identity;
}
public static RelyingParty GetRelyingPartyByName(string name)
{
ManagementService svc = ManagementServiceHelper.CreateManagementServiceClient();
RelyingParty relyingParty = svc.RelyingParties.Where(m => m.Name == name).FirstOrDefault();
return relyingParty;
}
public static Delegation CreateDelegation(long serviceIdentityId, long relyingPartyId, string userName, string identityProvider)
{
ManagementService svc = ManagementServiceHelper.CreateManagementServiceClient();
Delegation delegation = new Delegation()
{
NameIdentifier = userName,
IdentityProvider = identityProvider,
RelyingPartyId = relyingPartyId,
ServiceIdentityId = serviceIdentityId,
};
svc.AddToDelegations(delegation);
svc.SaveChanges();
return delegation;
}
(F) Parsley.com requests an access token from Acme.com/authserver/token (the authorization server's token endpoint) by authenticating using its client credentials, and includes the authorization code received in step (E).
After Parsley.com receives the authorization code, it can use the authorization code in exchange for an access token and an refresh token from Acme.com authorization server's token endpoint. Beside the authorization code, the request should also include Parsley.com's client credentials, a redirect URI for Parsley.com to receive the access/refresh tokens.
ACS Integration Points: none.
(G) Acme.com/authserver/ validates Parsley.com's client credentials and the authorization code and if valid, responds back with an access token and a refresh token.
Upon receipt of the access token request, Acme.com's authorization server will send a HTTP POST request to "acmebank" service namespace's ACS OAuth2 endpoint (https://acmebank.accesscontrol.windows.net/v2/OAuth2-13). ACS will return a refresh token and a short-lived acces token in a JSON object back to Parsley.com.
ACS Integration Points:
#4: POST a HTTP request to ACS OAuth2 endpoint for an access token
Clients such as Parsley.com can programmatically create an HTTP POST request containing the client credentials, the authorization code obtained in previous step, a redirect URI, and a grant type fixed to "authorization_code". The request may look like:
POST /v2/OAuth2-13 HTTP/1.1
Host: acmebank.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded
code=xxxxxxxxxxxxxx&
client_id=Parsley.com&
client_secret=xxxxxxxxxxxxxx&
redirect_uri=https://www.parsley.com/back&
grant_type=authorization_code
ACS returns the access token and refresh token in a JSON object in the response to the above request. The JSON object may look like:
{
"access_token":"xxxxxxxxxxxxxxxxxx",
"expires_in":3920,
"token_type":"Bearer",
"refresh_token":"xxxxxxxxxxxxxxxxxxxx"
}
Protected Resource Access Flow
The protected resource access flow illustrated in above diagram includes the following steps:
(H) Parsley.com retrieves Mary's account information (the protected resource) from Acme.com/accounts/ (the resource server) by providing the access token received in step (G).
After receiving the access token, Parsley.com can use it to call Acme.com's account service to fetch the protected resources, i.e. Mary's account data with Acme.com, on behalf of Mary. It sends an HTTPS GET request by including the access token as part of the query parameter (alternatively, Parsley.com can embed the token in the HTTP request's "Authorization" header).
ACS Integration Points: none.
(I) Acme.com/accounts validates Parsley.com's account access request and the access token and if valid, responds back with the request account information belonging to Mary.
Upon receipt of the account data request from Parsley.com, Acme.com's account service will parse the access token from the incoming request (either from the query parameter or the "Authorization" HTTP header) and validate the parsed token. On successful validation, it will return the requested account data back to Parsley.com.
ACS Integration Points:
#5: Validate access token in the account access request
Critically speaking, there is no ACS integration required in this step and it is Acme.com account service's sole responsibility to handle the access token parsing and validation. However, to bring a better develop experience to ACS OAuth2 delegation application developers, ACS would provide APIs to handle the parsing and validation of various ACS tokens embedded in the access token, such as Simple Web Token (SWT). (Note: even though ACS v2 still heavily uses SWT as a token format, there is not any official specification about SWT published.)
For example, in ACS OAuth2 delegation sample, it provides a SimpleWebTokenHandler and SimpleWebToken classes to help with handling SWT-typed access token processing.
Access Token Renew Flow
The access token renew flow illustrated in above diagram includes the following steps:
(J) Parsley.com (the client) sends the renew access token request to Acme.com/authserver/auth (the authorization server's authorization endpoint). Parsley.com includes its client credentials, the redirection URI and the refresh token received in step (G).
When the access token returned in step (G) expires, Acme.com's account service endpoint will respond with HTTP 401 Unauthorized. Parsley.com can use the refresh token (also returned in step (G)) to request a new short-lived access token from Acme.com authorization server's token endpoint.
ACS Integration Points:
#6: POST a HTTP request to ACS OAuth2 endpoint to renew the access token
Parsley.com can programmatically create an HTTP POST request containing the client credentials, the refresh token obtained in step (G), and a grant type fixed to "refresh_token" in exchange for a new set of access token and refresh token. The request may look like:
POST /v2/OAuth2-13 HTTP/1.1
Host: acmebank.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded
code=xxxxxxxxxxxxxx&
client_id=Parsley.com&
client_secret=xxxxxxxxxxxxxx&
grant_type=refresh_token
(K) Acme.com/authserver/ validates Parsley.com's client credentials and the refresh token and if valid, responds back with a new access token and a new refresh token.
Similar to above integration point #4, ACS returns the access token and refresh token in a JSON object in the response to the above renewal request.