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

  1. The user uses your application on some device.
  2. The device shows a short code and ask them to go to a URL (provide a link, perhaps with a QR Code).
  3. It goes to IROH and verifies that the code is the same.
  4. Authorize your application.
  5. Closes the browser and gets back to the device.
  6. The device shows a message and the account is connected.

Developer Workflow

  1. Create and activate a IROH account.
  2. Create the device grant client for your application.

Your device should be able to use the client credentials. For every user:

  1. Make a request to IROH with the client credentials to retrieve:
    • user_code
    • device_code
    • verification_uri_complete to send the user to
    • verification_uri to send the user without pre-filling the user code
  2. Show the user_code to the user and send the user to either verification_uri or verification_uri_complete.
  3. Concurrently, poll the IROH /iroh/oauth2/token API with the client credentials and the device_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.
  4. 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:

  1. Using the Cisco XDR UI
  2. Using the API (via the Swagger UI interface)

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:

  1. Open the whoami API Swagger UI interface: https://visibility.amp.cisco.com/iroh/profile/#!/Profile/get_iroh_profile_whoami.

  2. 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.

  3. Click Try it out to the right of Parameters.

  4. 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:

  1. Open the clients API Swagger UI: https://visibility.amp.cisco.com/iroh/oauth2-clients/index.html#/OAuth2Client/post_iroh_oauth2_clients_clients.
  2. Click Try it out to the right of Parameters.
  3. Edit the JSON under CreateClientParams with the configuration for the client. For a description of each field, refer to the section OAuth2 Client Model.
  4. 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 or verification_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:

  1. 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.

  1. 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