{"id":17464602,"url":"https://github.com/massi-ang/aws-iot-custom-authorizer-sample","last_synced_at":"2025-04-19T18:24:36.815Z","repository":{"id":42708098,"uuid":"276661770","full_name":"massi-ang/aws-iot-custom-authorizer-sample","owner":"massi-ang","description":"Sample implementation of Custom Authorizers.","archived":false,"fork":false,"pushed_at":"2025-03-29T08:29:44.000Z","size":1527,"stargazers_count":20,"open_issues_count":1,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T09:25:53.097Z","etag":null,"topics":["aws-iot","cdk","jwt","mqtt-authorizer"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit-0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/massi-ang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-02T14:02:55.000Z","updated_at":"2025-03-29T08:29:44.000Z","dependencies_parsed_at":"2025-03-29T09:23:02.367Z","dependency_job_id":"502948d3-c99d-4fa7-9b6c-4f934f3910ac","html_url":"https://github.com/massi-ang/aws-iot-custom-authorizer-sample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massi-ang%2Faws-iot-custom-authorizer-sample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massi-ang%2Faws-iot-custom-authorizer-sample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massi-ang%2Faws-iot-custom-authorizer-sample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/massi-ang%2Faws-iot-custom-authorizer-sample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/massi-ang","download_url":"https://codeload.github.com/massi-ang/aws-iot-custom-authorizer-sample/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249762343,"owners_count":21321914,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-iot","cdk","jwt","mqtt-authorizer"],"created_at":"2024-10-18T10:46:48.182Z","updated_at":"2025-04-19T18:24:36.782Z","avatar_url":"https://github.com/massi-ang.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Custom Authorizers\n\nThis repo illustrates how to create and configure AWS IoT Core custom authorizers and provides client sample for how to invoke them. We provide the sample code for 2 type of custom authorizers: for WebSocket connections and for MQTT connections. It is of course possible combine the two in a single authorizer, but we keep them separate for readability.\n\n## What are custom authorizers and why do you need them\n\nAWS IoT Core authenticates MQTT/TLS connections using mutual TLS and MQTT/Websocket connections using AWS IAM credentials. AWS IoT Core supports also the HTTP protocol, but in such case there is no bi-directional communication possible. If your application and device can support any of the above, it is highly recommended to use them. When using such authentication methods, the authorization associated to the connection is determined based on the AWS IoT Policies associated to the principal identity of the device: the certificate in case of MQTT/TLS and the Cognito Identity in case of MQTT/Websockets.\n\nFor all those cases where the above methods are not suitable, customers can create a custom authorizer implemented via a Lambda function.\n\n## Contents of this demo\n\nIn this demo we provide a sample implementation for:\n\n* A custom authorizer for [JWT tokens](#jwt-custom-authorizer-demo)\n* A custom authorizer for [MQTT username/password](#mqtt-custom-authorizer-configuration)\n\n## Prerequisites\n\n* An AWS Account\n\nOn the developer machine:\n* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html)\n* [ASW CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)\n\nFor the client:\n* Node.js 10 or later\n* jq (`sudo apt-get install jq`)\n\n\u003e **DISCLAIMER**: This solution is intended for demo purposes only and should not be used as is in a production environment without further works on the code quality and security.\n\n\n## Deploy the backend via CDK\n\nYou can run the provided CDK project to deploy 2 custom authorizer Lambda functions.\nYou can examine the definition of the resources that are going to be created in the `lib/jwt-iot-custom-authorizer-stack.ts` file. The authorizer logic in the Lambda functions in the `/lambda` folder. There are 2 authorizers, one for Websocket and one for MQTT.\n\nRun the following commands to download all the project dependencies and compile the stack:\n\n```\nnpm install\nnpm run build\n```\n\nThen deploy it with:\n\n```\ncdk deploy\n```\n\nYou can change the default values for the username, password and token for the MQTT authorizer by invoking the `cdk deploy` with the parameters:\n\n```\ncdk deploy --parameters username=admin --parameters password=admin --parameters token=XXX\n```\n\n**NOTE**: if this is the first time you use CDK on this AWS account and region the deploy will fail. Read the instructions printed on screen on how to bootstrap the account to be able to use the CDK tool.\n\nThe above commands will print out few lines ending with the 2 custom authorizer Lambda function arns, one called `lambdaArn` and the other `lambdaArnMqtt`. Please note these down as they will be needed later.\n\n\n## WebSocket Custom Authorizer for JSON Web Tokens (JWT)\n\nIf signature verification is enabled on the custom authorizer, AWS IoT Core validates that the token that is provided is signed with a an asymmetric key known by the client and AWS IoT Core. This prevents malicious users to trigger your custom authorizer Lambda function as AWS IoT Core will deny access if the token and the token signature do not match.\n\nAWS IoT Core supports signatures generated by the RSA256 algorithm. This is also one of the algorithms that can be used to sign JWT tokens [RFC 7518](https://tools.ietf.org/html/rfc7518#section-3), which means that if you have a signed JWT token, you can use the JWT as token and the JWT signature as the token signature to pass to the authorizer. In this way AWS IoT Core takes care of validating the signature and the Custom Authorizer Lambda function can trust the JWT and does not need to validate it again.\n\nNote that the JWS signature uses a base64url encoding schema (see https://datatracker.ietf.org/doc/html/rfc7515#section-7.1), while AWS IoT Custom authorizer require a base64 encoding. The difference between the two is that `-` and `_` characters are valid in base64url but not in base64 and must be replaced by `+` and `/` respectively. Moreover, padding `=` is not used in base64url. The file `client/javascript/token-gen.js` implements the code to create a signed JWT token and make the signature compliant to AWS IoT Core format.\n\nIf you want to use JWT tokens provided by 3rd parties IdP, verify that the signing algorithm used is RSA256. \nYou also need to get the public key from the provider that will be used as the public verification key by the custom authorizer. If the provider is OIDC compliant, you can obtain the public key from the jwks endpoint. (For an extensive walk through you can refer to [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/))\n\n1. `GET /.well-known/openid-configuration` from the provider endpoint (https://openid.net/specs/openid-connect-discovery-1_0-21.html#ProviderConfigurationRequest)\n1. Extract the `\"jwks_uri\"` value from the response\n1. `GET \u003cjwks_uri\u003e` to get the JSON Web Key Set (https://auth0.com/docs/security/tokens/json-web-tokens/json-web-key-set-properties)\n1. Derive the public key\n\nYou can use [jwks-rsa](https://www.npmjs.com/package/jwks-rsa) library to get the public key.\n\n# JWT custom authorizer demo\n\n## Create a custom signing key pair \n\nFor this demo we are going to create our own signing key pair using openssl. The private key of the signing pair is used to generate the token signature, the public key is assigned to the custom authorizer in AWS IoT Core and is used to verify the signature.\n\n```bash\nopenssl genrsa -out myPrivateKey.pem 2048\nopenssl rsa -in myPrivateKey.pem -pubout \u003e mykey.pub\n```\n\nThe file `mykey.pub` will contain the public key in PEM format that you will need to configure for the authorizer in the next step.\n\n##  Create the authorizer for WSS connections and JWT tokens\n\nIn this step we are going to configure the custom authorizer in AWS IoT Core. You can find more information about custom authorizers in the [documentation](https://docs.aws.amazon.com/iot/latest/developerguide/custom-authorizer.html).\n\nWe first create the authorizer, giving it a name and associating it with the lambda function that performs the authorization. This lambda function has been created when we executed the CDK script (`lambdaArn`). You can examine the code in `lambda/iot-custom-auth/lambda.js`.\n\n```bash\narn=\u003clambdaArn from CDK output\u003e\n\nresp=$(aws iot create-authorizer \\\n  --authorizer-name \"TokenAuthorizer\" \\\n  --authorizer-function-arn $arn \\\n  --status ACTIVE \\\n  --token-key-name token \\\n  --token-signing-public-keys KEY1=\"-----BEGIN PUBLIC KEY-----\n  ...\n  ...\n  -----END PUBLIC KEY-----\")\n\nauth_arn=$(echo $resp | jq -r .authorizerArn -)\n```\n\n**Note:** you can also use the AWS Console to create the Custom Authorizer\n\nWe need to give the AWS IoT service the permission to invoke this AWS Lambda function when a new connection request is made.\n\n```bash\naws lambda add-permission \\\n  --function-name  $arn \\\n  --principal iot.amazonaws.com \\\n  --statement-id Id-1234 \\\n  --action \"lambda:InvokeFunction\" \\\n  --source-arn $auth_arn\n```\n\nBy using the `source-arn` condition, we limit which custom authorizer can invoke this specific function.\n\n## Test the authorizer Lambda function\n\nWe use the AWS CLI to test if the authorizer is setup correctly. To obtain the token and the corresponding signature we use the `token-gen.js` helper.\n\n```\nnode client/javascript/helpers/token-gen.js --id \u003cid\u003e --key_path \u003cpath to private key\u003e\n``` \n\nThis will print out a JSON with a token and a signature. \n\n```\n{\n  \"token\": \"eyJhbGciO...Y3NTY3OTg0Nn0\",\n  \"signature\": \"ab7KFiLjFwuEeZHNYI...1uNX3Smw==\"\n}\n```\n\nCopy the two values and execute the following command:\n\n```bash\naws iot test-invoke-authorizer \\\n  --authorizer-name TokenAuthorizer \\\n  --token \u003ctoken\u003e --token-signature \u003csignature\u003e\n```\n\n### How permissions are generated\n\nThe JWT token used for this demo has the following format:\n\n```json\n{\n  \"sub\": \u003cid\u003e,\n  \"exp\": 1593699087\n}\n```\n\nThis token is base64 encoded and passed sent to the custom authorizer together with the signature. AWS IoT Core validate the signature against the token and then passes the token base64 encoded values to the Custom Authorizer  Lambda function.\nThe Lambda function checks the `exp` field to see if the token has not expired and uses the `sub` field in the token to scope down the policy for the connection, allowing the client to publish and subscribe to the topic `d/\u003csub\u003e` and to its own IoT Shadow (`$aws/things/\u003csub\u003e/shadow/*`).\n\n\n## Test with custom clients\n\nWe provide some sample client implementations in Javascript and Python. You find them in the `/client` folder.\n\n| Language | Client | Runtime | Notes |\n|---|---|----|---|\n| Javascript | `wss-client-v1.js` | NodeJs | uses the [v1 node sdk](https://github.com/aws/aws-iot-device-sdk-js)|\n| Javascript | `mqtt_wss_client.js` | NodeJs |  uses the `mqttjs` library. Requires NodeJs \u003e= 4.4 |\n| Javascript | `index.html` | Browser | uses the `mqttjs` library |\n| Python | `minimal-wss-client.py` | Python | uses AWS IoT Device client, connects via WebSockets |\n\n\nAll clients connect to AWS IoT Core using the `id` as the MQTT Client Id, they subscribe to the `d/\u003cid\u003e` topic and then start publishing messages to the same `d/\u003cid\u003e` topic.\n\nThe test client publishes a message to the topic `d/\u003csub\u003e` every 5 sec. \n\nYou can also use the [AWS Iot Test console](https://console.aws.amazon.com/iot/home?#/test) to check the messages are being received.\n\n### Running the clients \n\nThe Javascript clients generate the JWT token give the `id` and the signature.\n```\nnode client/javascript/wss-client-v1.js --key_path \u003ckey path\u003e --endpoint \u003cendpoint\u003e --id \u003cid\u003e [--verbose] [--authorizer_name] [--key_path]\n```\n\nFor all other clients you need to first generate the token and the signature values and then pass them as arguments to the client.\n\n```\nnode client/javascript/helpers/token-gen.js --id \u003cid\u003e --key_path \u003cpath to private key\u003e\n``` \n\nFor example, to invoke the WebSockets Python client:\n\n```\npython client/python/minimal-wss-client.py --key_path \u003ckey path\u003e --endpoint \u003cendpoint\u003e --id \u003cid\u003e [--verbose] [--authorizer_name] [--token_name] [--token] [--signature]\n```\n\nwhere:\n* **key_path** is the path to the private key PEM encoded file.\n* **endpoint** is the FQDN of your AWS IoT endpoint (get it via `aws iot describe-endpoint --endpoint-type iot:Data-ATS` on from the console).\n* **id** is the client id, thingName.\n* **verbose** prints out the encoded JWT token and signature.\n* **authorizer_name** in case you need to specify another authorizer than TokenAuthorizer.\n* **token_name** in case you need to specify another token key name than token.\n\nFor the python client and the browser client you need to pass the token and signature values, which can be obtained as follow:\n\n```\nnode client/javascript/helpers/token-gen.js --id \u003cid\u003e --key_path \u003cpath to private key\u003e\n``` \n\n## Testing with [aws-iot-device-sdk-cpp-v2](https://github.com/aws/aws-iot-device-sdk-cpp-v2)\n\nTo test the custom authorizer with the CPP device SDK v2 proceed as follow:\n\n* Clone the github repo\n* Compile the code following the instructions\n* execute the `samples/mqtt/raw-pub-sub` sample with the following args:\n```\n  --endpoint \u003ciot endpoint\u003e \n  --use_websocket --auth_params token=\u003ctoken\u003e,x-amz-customauthorizer-name=TokenAuthorizer,x-amz-customauthorizer-signature=\u003csignature\u003e --topic d/\u003cid\u003e\n```\n\nYou can get the `token` and `signature` values running \n```\nnode client/javascript/helpers/token-gen.js --id \u003cid\u003e --key_path \u003cpath to private key\u003e\n``` \nUse the same value for the `id` used to generate the token in the topic value passed to the client.\n\nYou can also use the Java SDK as it provides a [raw-pub-sub](https://github.com/aws/aws-iot-device-sdk-java-v2/tree/master/samples/RawPubSub) implementation.\n\n# About the tokens and security\n\nIn this demo the client is responsible of signing the token. This is not secure as a malicious actor gaining access to the private key could craft his own privileges or impersonate another device.\n\nThe token and its signature should therefore be generated in the backend, and possibly also encrypted. The token and the signature should then be provided to the device via another secure channel, eg via a companion app, a registration step, etc. which are not in the scope of this demo.\n\n\n# MQTT custom authorizer configuration\n\nIn this second demo we are going to setup a new custom authorizer to perform username and password authentication for MQTT/TLS connections.\n\n\u003e **NOTE**: the stack deploys the authorizer with some default values for username, password and token. You can, and should, change them by redeploying the stack with the following command:\n\n```\ncdk deploy --parameters username=\u003cvalue\u003e --parameters password=\u003cvalue\u003e --parameters token=\u003cvalue\u003e      \n```\n\n## Configure the authorizer\n\nThis Lambda function has been created by the CDK stack you have deployed. You can examine the code in `lambda/iot-mqtt-custom-auth/lambda.js`.\n\n```bash\narn=\u003clambdaArnMqtt arn from CDK\u003e\n\nresp=$(aws iot create-authorizer \\\n  --authorizer-name \"MqttAuthorizer\" \\\n  --authorizer-function-arn $arn \\\n  --status ACTIVE \\\n  --signing-disabled)\n\nauth_arn=$(echo $resp | jq -r .authorizerArn -)\n```\n\nWe need to give the AWS IoT service the permission to invoke this lambda function when a new connection request is made.\n\n```bash\naws lambda add-permission \\\n  --function-name  $arn \\\n  --principal iot.amazonaws.com \\\n  --statement-id Mqtt-auth \\\n  --action \"lambda:InvokeFunction\" \\\n  --source-arn $auth_arn\n```\n\n## Test the authorizer\n\nWe provide a Python client using the Python AWS CRT libraries.\n\n```\npip install -r requirements.txt\npython client/python/minimal-mqtt-client.py --username aladdin --password opensesame --topic d/aladdin --endpoint \u003cendpoint\u003e --authorizer-name MqttAuthorizer\n```\n\nWhere\n* **endpoint** is the FQDN of your AWS IoT endpoint (get it via `aws iot describe-endpoint --endpoint-type iot:Data-ATS` on from the console)\n\nThe difference from this code and the stock [pub-sub](https://github.com/aws/aws-iot-device-sdk-python-v2/blob/master/samples/pubsub.py) sample is in the initialization of the client, and in particular in the setup of the TLS context. \n\nThe relevant lines are the following:\n\n```python\ntls_options = io.TlsContextOptions()\ntls_options.alpn_list = ['mqtt']\n\nif args.root_ca:\n    tls_options.override_default_trust_store_from_path(ca_dirpath=None,\n        ca_filepath=args.root_ca)\ntls_ctx = io.ClientTlsContext(options=tls_options)\nclient = mqtt.Client(client_bootstrap, tls_ctx)\n\nusername = args.username\nif args.authorizer_name:\n    username += f'?x-amz-customauthorizer-name={args.authorizer_name}'\nif args.token:\n    username += f'\u0026token={args.token}'\nmqtt_connection = mqtt.Connection(client=client,\n    host_name=args.endpoint,\n    port=443,\n    on_connection_interrupted=on_connection_interrupted,\n    on_connection_resumed=on_connection_resumed,\n    client_id=args.client_id,\n    clean_session=True,\n    keep_alive_secs=6,\n    username=username,\n    password=args.password)\n\n```\n\nFor better compatibility with legacy clients, you might want to configure the authorizer as the default one for the account using the following command. This removes the need to append the custom authorizer name to the username:\n\n```\naws iot set-default-authorizer --authorizer-name MqttAuthorizer\n```\n\n## MQTT authorizer with Bearer token\n\nInstead of using username/password for MQTT authentication, you can also use a bearer token. In this case you will need to specify a token name when creating the authorizer and you can use the signing option to secure your endpoint. We are not going to enable signing in this example. \n\nLet's create a new authorizer using the same authorizer lambda that already has code to use the token:\n\n```bash\narn=\u003clambdaArnMqtt arn from CDK\u003e\n\nresp=$(aws iot create-authorizer \\\n  --authorizer-name \"MqttTokenAuthorizer\" \\\n  --authorizer-function-arn $arn \\\n  --status ACTIVE\n  --token-key-name mytoken\n  --signing-disabled)\n\nauth_arn=$(echo $resp | jq -r .authorizerArn -)\n\naws lambda add-permission \\\n  --function-name  $arn \\\n  --principal iot.amazonaws.com \\\n  --statement-id Id-1234 \\\n  --action \"lambda:InvokeFunction\" \\\n  --source-arn $auth_arn\n```\n\nThe difference compared to the username/password authorizer we created previously is that we have added the parameter `--token-key-name` when creating the authorizer.\n\nTo test it, execute the client with the following options:\n\n```bash\nendpoint=$(aws iot describe-endpoint --type data:iot-ats)\npython client/python/minimal-mqtt-client.py --endpoint $endpoint \\\n  --topic d/aladdin --token allow --token-name mytoken --authorizer-name MqttTokenAuthorizer \\\n  --username aladdin --password dummy\n```\n\nYou can also test the MQTT/TLS connection with the `raw-pub-sub` sample client available in the [Java](https://github.com/aws/aws-iot-device-sdk-java-v2) \nand [CPP](https://github.com/aws/aws-iot-device-sdk-cpp-v2) SDKs.\n\nWith those samples you need to pass the `token` and the `authorizer name` as part of the username. The password can be any string. The value for the username would then be:\n\n`aladdin?x-amz-customauthorizer-name=MqttTokenAuthorizer\u0026mytoken=allow`\n\n## Enable signing\n\nIf you enable signing for the authorizer you need to generate the token signature using the RSA256 algorithm and pass it as part of the username.\n\nFor example the full username would be similar to:\n\n`aladdin?x-amz-customauthorizer-name=MqttTokenAuthorizer\u0026token=allow\u0026x-amz-customauthorizer-signature=\u003csignature\u003e`\n\nWhere `\u003csignature\u003e` is the computed signature.\n\n## Test using the embedded C SDK\n\nTo test the MQTT/TLS custom authorizer with the [embedded C SDK](https://github.com/aws/aws-iot-device-sdk-embedded-C), you can use the `demos/mqtt/mqtt_demo_mutual_auth`.\n\n```\ngit clone https://github.com/aws/aws-iot-device-sdk-embedded-C\n```\n\nOpen the `demos/mqtt/mqtt_demo_mutual_auth\\demo_config.h` include file and specify values for:\n* CLIENT_PRIVATE_KEY_PATH\n* CLIENT_USERNAME\n* CLIENT_PASSWORD\n\nOpen the `demos/mqtt/mqtt_demo_mutual_auth\\demo_config.h` and change the value for:\n* MQTT_EXAMPLE_TOPIC\n\nto `d/\u003cusername\u003e` or whatever you have setup the policy returned by the custom authorizer to return.\n\nOnce done, in the terminal you would do:\n\n```\nmkdir build\ncd build\ncmake ..\nmake mqtt_demo_mutual_auth\nbin/mqtt_demo_mutual_auth\n```\n\n## Using Custom Domains\n\nWhen using Custom Domains for the AWS IoT Core endpoint, you need to configure the authorizer on the domain using the following API https://docs.aws.amazon.com/iot/latest/apireference/API_UpdateDomainConfiguration.html\n\n\n```\naws iot update-domain-configuration --domain-configuration-name \u003cDOMAIN_CONF_NAME\u003e \\\n     --authorizer-config defaultAuthorizerName=\u003cAUTHORIZER_NAME\u003e\n``` \n\n\n# Troubleshooting\n\n## Testing without signature verification\n\nIn case you encounter issues in connecting the client with AWS IoT Core, you can try configuring the authorizer without token signing.\n\n```bash\nresp=$(aws iot create-authorizer \\\n  --authorizer-name \"TokenAuthorizer_NoSign\" \\\n  --authorizer-function-arn $arn \\\n  --status ACTIVE \\\n  --token-key-name token \\\n  --signing-disabled\")\nauth_arn=$(echo $resp | jq -r .authorizerArn -)\n```\n\nAnd then add the lambda permissions:\n\n```bash\naws lambda add-permission \\\n  --function-name  $arn \\\n  --principal iot.amazonaws.com \\\n  --statement-id Id-12367 \\\n  --action \"lambda:InvokeFunction\" \\\n  --source-arn $auth_arn\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmassi-ang%2Faws-iot-custom-authorizer-sample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmassi-ang%2Faws-iot-custom-authorizer-sample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmassi-ang%2Faws-iot-custom-authorizer-sample/lists"}