IBM Verify

IBM Verify

Join this online user group to communicate across Security product users and IBM experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.

 View Only

OAuth: Device Flows

By Leo Farrell posted Tue July 03, 2018 12:00 AM

  

Introduction to Device Flows

As Internet of Things (IoT) devices become more prevalent, so does the importance of the way these devices interact with user information and the web. These devices often need to call APIs which require authentication, but cannot provide a suitable method of user interaction in order for traditional authentication mechanisms such as username/password. Because these devices intend to consume APIs, OAuth is a clear choice to secure the authorization; however, OAuth traditionally still requires browser interaction during authentication.

The OAuth device flow seeks to define a mechanism which solves this problem, providing the well defined security patterns of OAuth, while enabling an alternative method for the user to authenticate to the authorization server. The OAuth device flow is currently in Draft Version 10.

New in ISAM 9.0.5.0 is support for device flows.


User Experience

The device flow starts with a device requesting authorization. This results in two codes, one which is kept by the device(device_code) and the user_code which is displayed with a verification_uri  to the end user.

OAuth: Device Flows, Image 1

An example screen that a device might show to an end user. They may either manually enter the URL and the code, or they may choose to scan the QR code with a mobile device.

The user is prompted with instructions to visit the URL, and login. The user enters the code and if this is successful they may be prompted to consent to the devices request. After this, a success message is shown.


The three user interaction steps: 1, Authentication. 2, Prompting for a code. 3, The success page.

The three user interaction steps: Authentication, prompting for a code , and the success page.

 

While the user is performing these operations, the client is silently polling the /token endpoint of the authorization server. Once it polls the endpoint after the user_code is authorized, it will be issued a bearer token.

 

Device Flow Sequence

The diagram below shows the interactions between the entities; the device, the authorization server,  and the user. Step 3 is highlighted to show that this is a polling operation. Step 4 the flow of information is one way, as the user has viewed the device and used this to know the required input into their mobile device.

OAuth: Device Flows, Image 3

Details on each step are:

  1. The device, upon powering on or some other ‘ready’ event, makes a request to the device_authorize endpoint. This request includes a client_id and potentially a scope as defined by the OAuth 2.0 specification.
  2. The response from a successful request is returned to the client, this includes a device_codeuser_code and verification_uri. Additional parameters may be returned. At this point, the client displays the user_code and verification_uri, while keeping device_code a secret.
  3. The client begins polling the token endpoint of the authorization server. It uses the grant_type value of urn:ietf:params:oauth:grant-type:device_code. Using this grant type the client presents its secret device_code. The device may be told to slow_down if it is polling to fast, and the device_code may eventually expire, which would force the client to restart the flow.
  4. While the device is polling, it will display the verification_uri and user_code, to the end user. It may also choose to present this information in another manner, such as a QR code.
  5. At this point the user is involved. The user borwses to the verification_uri where they can enter the user_code. The authorization server prompts the user to authenticate and potentially consent to the device.
  6. Once authentication has completed, the device which is polling with the device_code will succeed and an OAuth 2.0 Bearer Token  is returned.

 

Configuring Device Flows with ISAM

To perform device flows on ISAM. An OpenID Connect and API Protection definition needs to be configured for device flows. On the API definition creation page check the “Device Grant” check box under the grant types heading.


OAuth: Device Flows, Image 4



Note: This assumes a configured web reverse proxy and other relevent components. For more on this visit the knowledge center for the pages on OpenID Connect and API protection and Reverse Proxy configuration.

An Example Client Implementation

Below is a python script which emulates a device:

#!/bin/python3

import requests
import urllib3
import json
import sys
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

######################################################################
#CONSTANTS. Update these with your own values
######################################################################
#Should be the protocol + hostname + port + junction of your definition
BASE_URI="https://www.myidp.ibm.com/mga"

#client_id to use in the flow
CLIENT_ID="myclientid"

#any scopes to include
SCOPE="scope1 scope2"
######################################################################

def main():
    rest_hdr= {"accept":"application/json"}

    error=None

    while(error == None or error == "expired_token"):

        token_uri = "{0}/sps/oauth/oauth20/token".format(BASE_URI)
        device_authorize= "{0}/sps/oauth/oauth20/device_authorize".format(BASE_URI)

        device_authorize_request_data={"client_id":CLIENT_ID, "scope":SCOPE } 
        print(chr(27) + "[2J")
        print("\n")
        print("\n")
        print("\tInitiating device flow.\n")

        #For details on this request: https://tools.ietf.org/html/draft-ietf-oauth-device-flow-09#section-3.1
        req = requests.post(url=device_authorize, params=device_authorize_request_data, headers=rest_hdr, verify=False)

        if(req.status_code != 200):
            print(req.text)
            print("\tError getting device_authorize endpoint. Exiting\n\n")
            sys.exit(1)
        #For details on this response: https://tools.ietf.org/html/draft-ietf-oauth-device-flow-09#section-3.2
        device_code = req.json()['device_code']
        user_code = req.json()['user_code']
        verification_uri = req.json()['verification_uri']

        print(
"""
\tReceived: \n\t\tDevice Code(A secret, not usually shown): {0}\n\t\tUser \
Code: {1}\n\t\tVerification_uri: {2}\n\n\t Visit the verification uri\
 and input the user code\n\n\n
"""
                .format(device_code,user_code,verification_uri))

        print('\n\tPolling for the token to be validated.', end='')
        df = {"client_id": CLIENT_ID,"grant_type": "urn:ietf:params:oauth:grant-type:device_code", 
                "device_code":device_code}

        bearer = None
        slower=1
        while(bearer == None and error != "expired_token"):
            #For details on this response: https://tools.ietf.org/html/draft-ietf-oauth-device-flow-09#section-3.4
            token = requests.post(url=token_uri,  data=df, verify=False)

            error = token.json().get("error")

            if (error == None):
                bearer = token.json()

            if(error == "slow_down"):
                slower +=1

            print(".", end="")
            sys.stdout.flush()
            time.sleep(slower)


        if bearer != None: 
            print("\n\tFlow successful")
            print("\n\t\tAccess Token: {0}".format(bearer["access_token"]))
            print("\n\t\tRefresh Token: {0}".format(bearer["refresh_token"]))
            print("\n\t\tScope: {0}".format(bearer["scope"]))
            break


if __name__ == "__main__":
    main()

Note: When running the above, if the verification_uri returned to the user is incorrect, update the pre-token mapping rule to return the correct value.

 

Running the Device Script

Example usage and expected output from the device script is provided below:

$ python3 device.py
   	Initiating device flow.


	Received:
		Device Code(A secret, not usually shown): jWSBZPbSAUKqNryGpnG41rls6TqSdL
		User Code: uuvw-z2pd
		Verification_uri: https://www.myidp.ibm.com/mga/sps/oauth/oauth20/user_authorize

	 Visit the verification uri and input the user code



	Polling for the token to be validated...............
	Flow successful

		Access Token: baafbggebagabaggagb

		Refresh Token: eagaefaeegefcbfdefeeegdageacdbceceaadeea

		Scope: scope1 scope2




Demonstration

 

Known Issues:

 

Issue Description APAR Work Around
Tokens issued do not have a lifetime http://www-01.ibm.com/support/docview.wss?crawler=1&uid=swg1IJ07345 Add a snippet to the post-token mapping rule which updates the lifetime of the token. Eg:

if(request_type == "access_token" && grant_type == "urn:ietf:params:oauth:grant-type:device_code") {
  var tokens = OAuthMappingExtUtils.getTokens(state_id);
  for(var i = 0 ; i < tokens.length; i++) {
    var t = tokens[i];
    var type = "" + t.getType();
    var lifetime = 604800 
    if(type == 'access_token') {
      lifetime = 3600;
    }
    var updated = OAuthMappingExtUtils.updateToken(t.getId(), lifetime,null,true);
  }
}

 

Thank you to Keiran for your help and input on this article.



#ISAM

0 comments
23 views

Permalink