AWS IoT & ForgeRock OpenAM Device OAuth 2.0 Flow

karthik
6 min readOct 24, 2018

--

AWS IoT has added a new feature using which devices can authenticate with a third-party Identity Provider and invoke the AWS IoT APIs to interact with other devices or APIs.

Use Case :

Build a IoT infrastructure using a enterprise Identity provider which supports Device OAuth 2.0 protocol. If an organization wants to build Apps for devices like TV, Gaming console or printer etc. which has limited input device and integrate with AWS IoT, OAuth 2.0 Device flow can be used.

  1. User selects the App in TV, Gaming Console, printer or similar devices which has limited input option, but connected to internet
  2. Device makes a OAuth2 API call to OpenAM’s oauth2/device/code endpoint with client_id and set of scopes
  3. OpenAM returns a response with the verification_url, user_code and device_code
  4. User opens a browser in another device like a mobile / laptop and opens the verification_url and OpenAM shows a screen where the user needs to enter the user_code
  5. User enters the user_code and gets redirected to OpenAM’s login page. User has to enter valid credentials and provide consent to share the requested scopes
  6. Device keeps polling OpenAM’s /oauth2/access_token endpoint using device_code, client_id and secret.
  7. If the user has authenticated successfuly in step 5, OpenAM will return access_token and refresh_token
  8. Device makes a API call to AWS IoT service with token_signature, access_token and message
  9. AWS IoT Gateway calls the custom Lambda Authorizer to validate the access_token
  10. If the token is valid, Lambda Authorizer returns a policy which allows the AWS IoT Gateway to post a message the target IoT Topic
  11. AWS IoT evaluates the policy
  12. a) If policy is Deny, then 403 is returned to the device , b) if policy is Allow, it posts a message to the IoT Topic

There are few prerequisites for setting up this integration:

  1. AWS Account — business or free tier.
  2. Knowledge on AWS IoT, AWS Lambda, Node.js
  3. Knowledge on OAuth2 protocol

We have to setup the following components:

  1. Local ForgeRock OpenAM instance and OAuth service setup
  2. Create AWS Lambda Custom Authorizer
  3. AWS IoT Custom Authorizer setup

Step 1 : Setup local ForgeRock OpenAM instance and configure OAuth service

  • Add a local hosts file entry for 127.0.0.1 openam.example.com
  • Download the OpenAM 13.5 war from ForgeRock website and rename it to idpam.war
  • Follow this guide to setup a local OpenAM instance. Use http://openam.example.com:8080/idpam as the FQDN instead of http://openam.example.com:8080/openam
  • Create a new realm “awsiot”
  • Follow OpenAM developer guide and enable OAuth2 Provider service
  • Enable “ Use Stateless Access & Refresh Tokens”
  • For this POC, we will be using the OAuth2 token signing algorithm as HS256. It is advisable to use RS256 in production systems. Decide a HMAC Shared secret value, base64 encode that secret and update the value
  • Save the changes
  • Create a OAuth agent in this realm with client_id “deviceagent”. Note down the client_secret as well for testing.

Step 2 : Create AWS Lambda Custom Authorizer

  • Clone this project https://github.com/awskarthik82/aws-iot-openam
  • This is Node.js Lambda function which verifies the OpenAM’s access token using the HMAC Shared secret and return a Allow or Deny policy to AWS IoT
  • I used Auth0 Node.js JWT library to verify the tokens
  • Create a zip file of node_modules folder and iot-custom-authorizer.js file alone
  • Create a new AWS Lambda function with Runtime as Node.js 8.10 and upload this zip file. Also, change the Handler to iot-custom-authorizer.handler.
  • Also, define a Environment variable “key_secret” with HMAC Shared secret value used in OpenAM for the stateless access_tokens
  • Create a test to make sure the Lambda function is working as expected
  • Test this function and you should see a successful response with a Deny policy since the token is invalid. As long as the function returns a successful response, it is fine. If not, check the Lambda configuration.

Step 3 : AWS IoT Custom Authorizer setup

  • Create a key pair using below commands. Private key will be used to sign the token that is sent to AWS IoT endpoint and public key needs to be configured in AWS IoT Custom Authorizer to verify the signature
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
  • Create a custom AWS IoT Authorizer by passing the lambda function arn and public key contents
aws iot create-authorizer --authorizer-name iot-openam-authorizer --authorizer-function-arn <lambda_arn> --token-key-name token --token-signing-public-keys FIRST_KEY=" 
-----BEGIN PUBLIC KEY-----
<public.pem file content>
-----END PUBLIC KEY-----" --status ACTIVE
  • check if the authorizer got created successfully by executing below command
aws iot describe-authorizer --authorizer-name  iot-openam-authorizer
  • Configure a default authorizer that needs to be used. This needs to be configured since AWS IoT can use a default authorizer to check whether a request should be allowed or denied in case the custom authorizer name is not sent in request header
aws iot set-default-authorizer --authorizer-name  iot-openam-authorizer
  • Add permissions for AWS IoT to invoke the lambda function. This permission is required because for each request AWS IoT will invoke the lambda function to validate the token in the header
aws lambda add-permission --function-name "iot-authorizer" --principal iot.amazonaws.com --source-arn "<lambda_arn>" --statement-id Id-123 --action "lambda:InvokeFunction"

Step 4 : Testing

  • First, we have to simulate a device getting access_token from OpenAM
  • Invoke this command to initiate the device OAuth flow. In real life, this URL should be invoked by the device when the user opens a app or clicks a button
curl   --data response_type=token   --data scope=mail%20cn   --data client_id=deviceagent   http://openam.example.com:8080/idpam/oauth2/device/code?realm=awsiot
  • It should respond with user_code, device_code and verification_url
  • Open a browser and navigate to verification_url. Enter the user_code to start the authentication process
  • OpenAM will redirect to login page. Enter valid credentials and accept the consent. If everything is successful, OpenAM will show the below screen
  • Make a token API call to OpenAM to get access_token, refresh_token. Please note that both access_token and refresh_token should be in JWT format. If not, please check if stateless tokens are enabled in OpenAM’s OAuth2 service.
curl \
--data client_id=deviceagent \
--data client_secret=<client_secret> \
--data grant_type=http://oauth.net/grant_type/device/1.0 \
--data code=<device_code> \
http://openam.example.com:8080/idpam/oauth2/access_token?realm=awsiot
  • Create a signature for the access_token using openssl and the private key
echo -n "<jwt access_token>" | openssl dgst -sha256 -sign private.pem | openssl base64
  • Try posting a message to the IoT topic
curl -X POST -k -i -N -H "Host: <aws_iot_endpoint>" -H "X-Amz-CustomAuthorizer-Name: iot-openam-authorizer" -H "X-Amz-CustomAuthorizer-Signature: <token_signature>" -H "token: <access_token>" -H "User-Agent: aws-cli/1.10.65 Python/2.7.11 Darwin/16.1.0 botocore/1.4.55" --data 'Test message from openam protected device' https://<your_aws_iot_endpoint>/topics/customauthtesting
  • To check these messages, go to AWS IoT Core service > Test
  • Select “Subscribe to topic” and enter “customauthtesting”
  • Now, again try posting a message using the previous curl command
  • I posted two messages and it instantly showed up in the console
  • Any devices or applications subscribed to this particular topic will receive this message

We have successfully integrated a device with AWS IoT using the OAuth 2.0 token generated using ForgeRock OpenAM. This can be used in multiple use cases where the access_token can be used to access APIs which are secured using OpenAM’s OAuth tokens.

Thanks for reading this article. If you have any questions / suggestions, kindly leave a comment.

--

--

Responses (1)