OAuth 2 Authorization Code grant in ACS - nordvall/TokenClient GitHub Wiki
Overview
Also see OAuth 2 in ACS.
ACS has support for playing the Authorization Server role in the OAuth 2 Authorization code flow. Because ACS has no knowledge of your users passwords and what data to approve access to, you need to implement the Resource Server parts in your own application.
When users give permission for an app, the Resource Server register a so called Delegation in ACS. The delegation consist of:
- The user id and identity provider of the user that has given permission
- The Client that is given permission to
- The Resource Server given permission to
- Optionally, an arbitrary permission string, mostly called "scope" in OAuth
After the delegation is in place ACS can generate authorization codes that your Resource Server hand off to your clients. The clients then procees with the authorization codes as a normal OAuth flow.
Prerequisities
- The resource server needs to be configured for federated authentication with ACS.
- The resource server needs to have a Management Credetial registered in ACS, to be able to register its clients and delegations.
- The Client needs to be registered as a Service Identity in ACS, and have these attributes set:
-
- The name
-
- The redirect url
-
- A password
When a user approves permission, you need to obtain:
- The nameidentifier claim of the user
- The identityprovider claim
...as they are needed to register the delegation. They can be obtained from the logged in user's claims.
For information about obtaining management credentials and registering clients, see ACS administration.
Running the flow
1. Request permissions from the Resource Owner
The Resource Server need to implement an OAuth 2 authorization endpoint in its web site. The endpoint address is, according to the OAuth specification, in following form: ?response_type=code&client_id=abc&redirect_uri=def&scope=ghi
Here are the steps that needs to be performed when a user arrives at the endpoint:
1.1 Resource Server: Ensure that the Resource Owner is logged in
This is done by your ordinary login mechanism.
1.2 Resource Server: Check if the delegation is already created in ACS
The delegation needs to have these attribues:
- user: the logged in user id
- identity provider: the identity provider claim of the logged in user
- client: the Int64 id of the client found in client_id parameter
- resource: the Int64 id of the Resource Server, as registered under Relying Parties in ACS
- Permission: the permission level found in the scope parameter.
1.2.1 Look up the id of the Client
To be able to find this out, first you need to get the ACS Int64 Id of the Client:
Request:
GET /v2/mgmt/service/ServiceIdentities?$filter=Name eq 'clientname' HTTP/1.1
Authorization: Bearer *token*
Host: xxx.accesscontrol.windows.net
Accept: application/json
Parameters
parameter | value | example |
---|---|---|
Name | The name of the Client, as registered under Service Identities in ACS | ToDoApp |
Response:
HTTP/1.1 200 OK
{ "d" : { "results": [
{ "Id": "20628923", "Name": "clientname", "Description": "test", "RedirectAddress": http://client.com, "SystemReserved": false, "Version": "AAAAAAAvj4c=" }
] } }
The ServiceIdentityId is 20628923.
1.2.2 Look up the id of the Resource Server
Then you need to get the ACS Int64 Id of the Resource:
Request:
GET /v2/mgmt/service/RelyingParties?$filter=Name eq 'ResourceName' HTTP/1.1
Authorization: Bearer *token*
Host: xxx.accesscontrol.windows.net
Accept: application/json
Parameters
parameter | value | example |
---|---|---|
Name | The name of the Resource Server, as registered under Relying partyies in ACS. | ToDoSite |
Response:
HTTP/1.1 200 OK
{ "d" : {"results": [
{ "Id": "20629044", "Name": "ToDoApp", "DisplayName": null, "Description": null, "TokenType": "JWT", "TokenLifetime": 600, "AsymmetricTokenEncryptionRequired": false, "SystemReserved": false }
] } }
The RelyingPartyId is 20629044.
1.2.3 Look for an existing delegation
Now you can query for the delegation:
GET /v2/mgmt/service/Delegations?$filter=RelyingPartyId eq 20629044 and ServiceIdentityId eq 20628923 and NameIdentifier eq 'username' and IdentityProvider eq 'identityrealm' and Permissions eq 'Read' HTTP/1.1
Authorization: Bearer *token*
Host: xxx.accesscontrol.windows.net
Accept: application/json
Parameters
parameter | value | example |
---|---|---|
RelyingPartyId | The Int64 id of the Resource server, as obtained above. | 20629044 |
ServiceIdentityId | The Int64 id of the Client, as obtained above. | 20628923 |
NameIdentifier | The claims user name of the user. You can find it in the nameidentifier claim of the logged in user. | [email protected] |
IdentityProvider | The identityprovider claim of the currently logged in user | http://adfs.exampe.com/some/uri |
Permissions | The permission level that the Client asks for. You find it in the "scope" query string value | Read |
Delegation exists:
HTTP/1.1 200 OK
{ "d" : { "results": [
{ "Id": "20000164", "ServiceIdentityId": "20628923", "RelyingPartyId": "20629044", "IdentityProvider": "identityrealm", "NameIdentifier": "username", "Permissions": "Read", "AuthorizationCode": "YvKqykniq2hj7hxhp7pFjQ==" }
] } }
The Authorization code is YvKqykniq2hj7hxhp7pFjQ==
Delegation does not exist:
HTTP/1.1 200 OK
{ "d" : { "results": [] } }
1.3 Resource Server: Ask the user for permission
This step needs to be performed if there is no delegation registered in ACS.
This is done by showing the Resource Owner an UI dialog that ask him to give permission for the Client to access the data.
1.4 Resource Server: Create the delegation
This is done if the Resource owner approves the permission.
The Resource Server then registers the delegation in ACS.
Request:
POST /v2/mgmt/service/Delegations HTTP/1.1
Authorization: Bearer *token*
Host: xxx.accesscontrol.windows.net
Accept: application/json
Content-Type: application/json
{ "ServiceIdentityId": "20628923", "RelyingPartyId": "20629044", "IdentityProvider": "identityrealm", "NameIdentifier": "username", "Permissions": "Read" }
Parameters:
See under "1.2.3 Look for an existing delegation"
Response:
HTTP/1.1 201 Created
{ "d" : { "Id": "20000171", "ServiceIdentityId": "20628923", "RelyingPartyId": "20629044", "IdentityProvider": "identityrealm", "NameIdentifier": "username", "Permissions": "Read", "AuthorizationCode": "OBt8lExB8ZMMXsonQi6hRg==" } } }
The authorization code is OBt8lExB8ZMMXsonQi6hRg==
2 Return authorization code to Client
Now the Resource Server can redirect the user to the address found in redirect_uri, with the UrlEncoded Authorization Code appended. By OAuth definition the url is in the following format:
?code=OBt8lExB8ZMMXsonQi6hRg%3D%3D
3 Client: Ask ACS for an access token
The client extracts the access code from the returned URL and builds the following token request:
POST /v2/oauth2-13/ HTTP/1.1
Host: xxx.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Content-Length: 193
grant_type=authorization_code&client_id=abc&client_secret=123&redirect_uri=http://myapp.com&code=OBt8lExB8ZMMXsonQi6hRg%3D%3D&scope=http%3A%2F%2Ftodoapp.com%2F
Parameters:
Name | Value | Example |
---|---|---|
grant_type | the OAuth 2 grant type | Always authorization_code in this case. |
client_id | the ID of the Client, registered as Service Identity in ACS | user1 |
client_secret | One of the passwords of the client, registered for the Service Identity in ACS | pasword1 |
redirect_uri | The url to the Client application, as registered in the redirect_uri attribute of the Service Identity in ACS | http://myapp.com |
scope | the application you need an access token for | http://todoapp.com |
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"token_type":"urn:ietf:params:oauth:token-type:jwt",
"access_token":"*token*",
"expires_in":"599",
"refresh_token":"y8Bi38tP6h5u14pdu5zaZg==",
"scope":"http://todoapp.com/"
}
Now you can grab the access_token and use it for 599 seconds. After that, you can use the refresh_token to get another access token.
4. Client: Use the refresh token to renew the access token
Request:
POST /v2/oauth2-13/ HTTP/1.1
Host: xxx.accesscontrol.windows.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 193
grant_type=refresh_token&client_id=abc&client_secret=123&redirect_uri=http://myapp.com&refresh_token=y8Bi38tP6h5u14pdu5zaZg%3D%3D&scope=http%3A%2F%2Ftodoapp.com%2F
Parameters:
Parameter | Value | Example |
---|---|---|
grant_type | the OAuth 2 grant type | Always refresh_token in this case |
client_id | the id of the Client, as registered under ServiceIdentities in ACS | user1 |
client_secret | one of the valid passwords of the Client, must match the client of the original token request | password1 |
redirect_uri | the redirect uri of the client, as registered for the Client in ACS | http://myapp.com |
refresh_token | the refresh token you received when you got your first access token | 897bcdiuy8327 |
scope | the application you need an access token to, must match the scope from the initial token request | http://todoapp.com |
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"token_type":"urn:ietf:params:oauth:token-type:jwt",
"access_token":"*token*",
"expires_in":"599",
"scope":"http://todoapp.com/"
}
Note that there is no new refresh token supplied this time, but you can continue to use the old refresh token multiple times.