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.

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

Details on each step are:
- 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.
- The response from a successful request is returned to the client, this includes a
device_code
, user_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.
- 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.
- 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.
- 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.
- 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.

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