OAuth2 Device Grant
From the RFC 8628:
The OAuth 2.0 device authorization grant is designed for Internet- connected devices that either lack a browser to perform a user-agent- based authorization or are input constrained to the extent that requiring the user to input text in order to authenticate during the authorization flow is impractical. It enables OAuth clients on such devices (like smart TVs, media consoles, digital picture frames, and printers) to obtain user authorization to access protected resources by using a user agent on a separate device.
User Workflow
- The user uses your application on some device.
- The device shows a short code and ask them to go to a URL (provide a link, perhaps with a QR Code).
- It goes to IROH and verifies that the code is the same.
- Authorize your application.
- Closes the browser and gets back to the device.
- The device shows a message and the account is connected.
Developer Workflow
- Create and activate a IROH account.
- Create the device grant client for your application.
Your device should be able to use the client credentials. For every user:
- Make a request to IROH with the client credentials to retrieve:
user_code
device_code
verification_uri_complete
to send the user toverification_uri
to send the user without pre-filling the user code
- Show the
user_code
to the user and send the user to eitherverification_uri
orverification_uri_complete
. - Concurrently, poll the IROH
/iroh/oauth2/token
API with the client credentials and thedevice_code
. Make a call every few seconds (no more than once per second).- You will receive an "authorization pending" response if the user has not yet completed the authorization.
- If the user denies your application, you will get the appropriate error message.
- If the user authorizes your application, you will receive both an access and a refresh token.
- If step 3 succeeds, save the refresh token to get a new access token to call IROH on behalf of the user.
Create a Client
There are two ways to create a client:
Both ways will result in the same outcome.
Using the Cisco XDR UI
Device Grant Client creation is not yet supported by Cisco XDR UI.
Using the API (via the Swagger UI Interface)
The Swagger UI interface will provide raw access to the API to create a client. However, the only way to work with this API is by using a JWT with the oauth
scope.
Retrieve the list of your scopes. You will not be able to create a client with more scopes than you’re allowed to access.
To get the list of scopes, use the /iroh/profile/whoami
API:
Open the
whoami
API Swagger UI interface: https://visibility.amp.cisco.com/iroh/profile/#!/Profile/get_iroh_profile_whoami.If you are already logged in to https://visibility.amp.cisco.com/, you will not need to enter any information in the Authorization header inputs in Swagger. If not, click Authorize and provide authorization either with a IROH login, apiKey, or OAuth2 credentials.
Click Try it out to the right of Parameters.
Click Execute.
Under Server response, assuming you received a HTTP status code of 200, you will find the response from the API request. It is a JSON containing your user and org information including the list of your scopes.
Here is example response from the /iroh/profile/whoami
API:
{
"user": {
"scopes": [
"admin",
"casebook",
"cisco",
"collect",
"enrich",
"global-intel:read",
"inspect",
"integration",
"oauth",
"private-intel",
"profile",
"response",
"ui-settings"
"users"
],
"updated-at": "2019-04-30T13:54:21.641Z",
"user-email": "dev.null@cisco.com",
"org-id": "13375cf9-561c-4958-0000-6d84b7ef09d4",
"user-id": "idb-amp:13375ee9-2e3a-4e1b-977d-961facb5fd84",
"idp-mappings": [{
"idp": "idb-amp",
"user-identity-id": "13375ee9-2e3a-4e1b-977d-961facb5fd84",
"organization-id": "13375cf9-561c-4958-0000-6d84b7ef09d4"
}],
"enabled?": true,
"last-logged-at": [
"2019-04-30T13:54:21.843Z"
],
"created-at": "2019-04-30T13:54:21.622Z"
},
"org": {
"scim-status": "activated",
"name": "IROH Testing",
"enabled?": true,
"id": "13375cf9-561c-4958-0000-6d84b7ef09d4",
"created-at": "2019-04-30T13:54:21.634Z"
}
}
For a description of each field, refer to the section User Model under the Data Model page.
Now that you know which scopes you have access to, you can now create the client.
To use the /iroh/oauth2-clients/clients
API:
- Open the
clients
API Swagger UI: https://visibility.amp.cisco.com/iroh/oauth2-clients/index.html#/OAuth2Client/post_iroh_oauth2_clients_clients. - Click Try it out to the right of Parameters.
- Edit the JSON under CreateClientParams with the configuration for the client. For a description of each field, refer to the section OAuth2 Client Model.
- Click Execute.
Under Server response, assuming you received a HTTP status code of 200, you will find the response from the API request. For a description of each field in the response, refer to the section OAuth2 Client Model.
Create a Device Grant Client
Once you verify that the set of scopes for your client is correct, you can create a device grant client.
Here is an example API request to /iroh/oauth2-clients/clients
using a cURL command:
ACCESS_TOKEN="eyJhbG..."
curl -X 'POST' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'User-Agent: ob-http' \
-H 'Authorization: Bearer $ACCESS_TOKEN' \
-d '{
"scopes": [ "profile", "inspect" ],
"description": "Developer Doc OAuth2 Device Test Client",
"name": "OAuth2 Developer Doc Test",
"grants": [ "device-grant" ]
}' \
'https://visibility.amp.cisco.com/iroh/oauth2-clients/clients'
The server should then return a successful response:
{
"scopes": [
"profile",
"inspect"
],
"description": "Developer Doc OAuth2 Device Test Client",
"approved?": true,
"redirects": [],
"availability": "org",
"password": "WJj1uXCzB8TioC4HA8ISIQFTuwYtJK_4tqDR4JHZ92E-hTa0uMu-Gg",
"name": "OAuth2 Developer Doc Test",
"org-id": "org-1",
"enabled?": true,
"grants": [
"device-grant"
],
"client-type": "confidential",
"id": "client-5e7f8e3f-50e2-4139-86da-9e50bafca75a",
"approval-status": "approved",
"owner-id": "org-1-master-1",
"created-at": "2021-07-21T13:54:04.989Z"
}
Make sure to keep the client password securely. The client password can only be displayed once. After it is created, there is NO WAY to recover the client password. You will need to create another client if the credentials are lost.
For details about the client model, see OAuth2 Client Model.
Use the Client in the Device
Once the user uses your device and wants to connect its account to your application, make a request to the /iroh/oauth2/device_authorization
API (return line here is for readability only):
curl -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: ob-http' \
-d 'client_id=client-5e7f8e3f-50e2-4139-86da-9e50bafca75a
&client_secret=WJj1uXCzB8TioC4HA8ISIQFTuwYtJK_4tqDR4JHZ92E-hTa0uMu-Gg' \
'https://visibility.amp.cisco.com/iroh/oauth2/device_authorization'
The server should then return a successful response:
{
"device_code": "qF31oSsTV85uOEejoA2TZ3XHSw_0WblH6OPLmKepHzaP63VIjaRBWQ",
"user_code": "3FB39358",
"verification_uri": "https://visibility.amp.cisco.com/iroh/oauth2/device",
"verification_uri_complete": "https://visibility.amp.cisco.com/iroh/oauth2/device?user_code=3FB39358",
"expires_in": 600,
"interval": 1
}
Next, your application should:
- Display the
user_code
to the user. - Redirect the user to either
verification_uri
orverification_uri_complete
. For example, you could use a QR Code if your device does not have a good browser with an easy way to get user input. - Keep the
device_code
secret.
Poll the /token Request
Every second or so, you should make a request to the /iroh/oauth2/token
API to try to retrieve the user credentials. Repeat the request until you get a clear answer or a clear error.
Pending Authorization
While the user is going to the verification URI and authorizing the client, you will probably receive an "Authorization Pending" response (return line here is for readability only):
curl -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: ob-http' \
-d 'client_id=client-5e7f8e3f-50e2-4139-86da-9e50bafca75a
&client_secret=WJj1uXCzB8TioC4HA8ISIQFTuwYtJK_4tqDR4JHZ92E-hTa0uMu-Gg
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=qF31oSsTV85uOEejoA2TZ3XHSw_0WblH6OPLmKepHzaP63VIjaRBWQ' \
'https://visibility.amp.cisco.com/iroh/oauth2/token'
Here is an example response when the authorization is still pending:
{
"client-id": "client-5e7f8e3f-50e2-4139-86da-9e50bafca75a",
"error_uri": "https://datatracker.ietf.org/doc/html/rfc8628#section-3.5",
"trace_id": "8492398c-2651-43a5-9cb8-cf26ebf3628a",
"error": "authorization_pending",
"error_description": "Authorization pending"
}
Client Authorized
If the user authorizes your client, you will get a successful result that will contain both an access and a refresh token. The access token can be used to call IROH on behalf of the user and you can retrieve a new access token from the refresh token (return line here is for readability only):
curl -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: ob-http' \
-d 'client_id=client-5e7f8e3f-50e2-4139-86da-9e50bafca75a
&client_secret=WJj1uXCzB8TioC4HA8ISIQFTuwYtJK_4tqDR4JHZ92E-hTa0uMu-Gg
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=qF31oSsTV85uOEejoA2TZ3XHSw_0WblH6OPLmKepHzaP63VIjaRBWQ'
'https://visibility.amp.cisco.com/iroh/oauth2/token'
Here is an example response when the client has been authorized:
{
"access_token": "eyJhbGciOiJS...",
"scope": "profile inspect",
"token_type": "bearer",
"expires_in": 600,
"refresh_token": "eyJhbGciOiJSUzI1..."
}
Client denied or other issue
If the user denies your client access or if there are other issues, you will get a clear error message:
curl -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: ob-http' \
-d 'client_id=client-e74de10b-aad1-44b1-bb5d-69b95d359ae9
&client_secret=_cl_82jMo8z_0_476aI_nfS-mbVB0IjgVYhGE2FWiKSSjyahwLgiwA
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=spQSBvYP6r33cxqOvnCc7YfwkVw6rkpoLMeKTYFW4-PrU-G6N7WApA'
'https://visibility.amp.cisco.com/iroh/oauth2/token'
Here is an example response when the client has been denied:
{
"client-id": "client-5e7f8e3f-50e2-4139-86da-9e50bafca75a",
"error_uri": "https://datatracker.ietf.org/doc/html/rfc8628#section-3.5",
"trace_id": "65ff0978-bb54-434f-b5a3-41163d6082d0",
"error": "access_denied",
"error_description": "The user rejected the access"
}
Note: Other problems could occur. For example, the user could lack sufficient privileges to authorize your client, such as not enough scopes or the device code will expire after awhile.
For every error, you should get a clear message that the authorization will not occur.
Further attempt or after expiration
After the user either authorized, denied, or the device code expires, you should get a device code not found error:
curl -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: ob-http' \
-d 'client_id=client-e74de10b-aad1-44b1-bb5d-69b95d359ae9
&client_secret=_cl_82jMo8z_0_476aI_nfS-mbVB0IjgVYhGE2FWiKSSjyahwLgiwA
&grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=spQSBvYP6r33cxqOvnCc7YfwkVw6rkpoLMeKTYFW4-PrU-G6N7WApA'
'https://visibility.amp.cisco.com/iroh/oauth2/token'
Here is an example response of the "device code not found" error:
{
"client-id": "client-e74de10b-aad1-44b1-bb5d-69b95d359ae9",
"error_uri": "https://datatracker.ietf.org/doc/html/rfc8628#section-3.5",
"trace_id": "85f8c12d-4e64-474d-bb79-1666a479c5fa",
"error": "invalid_device_code",
"error_description": "device code not found"
}
Get a New Access Token
The access token will expire (default is 10 minutes). API access after token expiration will require user interaction. You will need to use the refresh token of the user to get a new access token.
The request to refresh a token uses the same endpoint you used to get it the first time. But instead of using a grant_type
of authorization_code
, you will use a grant_type
equal to refresh_token
(cf. RFC6749 section 6).
Example request:
curl -X POST \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_PASSWORD" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=REFRESH_TOKEN" \
'https://visibility.amp.cisco.com/iroh/oauth2/token'
The server should then return a successful response:
{
"access_token":"eyJhbG...",
"token_type":"bearer",
"expires_in":600
}
IMPORTANT: The response might also contain a new refresh token along with the new access token. In that case, you must revoke the old refresh token and use the new one instead. IROH might accept the old refresh token for a short period to prevent bugs in distributed environments.
Tutorial
This section provides complete example. The credentials used will not be functional, but you should be able to follow all the steps with your own credentials.
Clone Demo project
Clone the demo project from: https://github.com/yogsototh/oauth2-client-demo.
Create the Client
Use the /iroh/oauth2-clients/clients
API to create the client.
The following is an example API request to /iroh/oauth2-clients/clients
using a cURL command:
ACCESS_TOKEN="eyJhbGciO..."
curl -X 'POST' \
\
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'User-Agent: ob-http' \
-H 'Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"scopes": [ "profile", "inspect" ],
"description": "Developer Doc OAuth2 Device Test Client",
"name": "OAuth2 Developer Doc Test",
"grants": [ "device-grant" ]
}' \
'https://visibility.amp.cisco.com/iroh/oauth2-clients/clients'
The server should then return a successful response:
{
"scopes":[
"profile",
"inspect"
],
"description":"Developer Doc OAuth2 Device Test Client",
"approved?":true,
"redirects":[
],
"availability":"org",
"password":"WJj1uXCzB8TioC4HA8ISIQFTuwYtJK_4tqDR4JHZ92E-hTa0uMu-Gg",
"name":"OAuth2 Developer Doc Test",
"org-id":"org-1",
"enabled?":true,
"grants":[
"device-grant"
],
"client-type":"confidential",
"id":"client-5e7f8e3f-50e2-4139-86da-9e50bafca75a",
"approval-status":"approved",
"owner-id":"org-1-master-1",
"created-at":"2021-07-21T13:54:04.989Z"
}
Run the Demo
There are two demos that show how to use the Device Grant flow with IROH. The code source of both should be short enough to be easy to understand:
- Browser Demo
Edit the site/device-flow.html
file to change the value of the variables SX
, CLIENT_ID
and CLIENT_SECRET
to match the values for your client. Open the file site/device-flow.html
in your browser and follow the instructions.
- Shell Script Demo
There is an easy to read shell script demo you can launch. Edit the shell script with your client credentials and run the script.
./device-flow-demo.sh