Easy authentication for your distributed app with Daml and Auth0
|This post may contain outdated information. Please visit Secure Daml Infrastructure for more up-to-date content.|
Daml ledgers that are connected to the public internet need to be protected against unauthorized access. This is done using the same mechanism that is used to authorize the access to the backend of a regular web application: The ledger operator provides a token issuer service where users can authenticate themselves and acquire access tokens. Applications attach these tokens to each ledger API request, and the ledger uses the tokens for authorization checks.
Daml ledgers can be configured to work with arbitrary access token formats, and it is the responsibility of the ledger operator to provide a matching token issuer service. Managed Daml offerings provide such token issuer services and authorization checks out of the box.
In this blog post, we’ll have a look at how to create a custom authorization setup. We’ll use:
- For the ledger: The Daml sandbox, which comes with integrated authorization checks based on JWT tokens with custom authorization claims.
- For the token issuer: auth0.com, a third-party solution for issuing custom access tokens and authenticating users against social identity providers such as google. We have chosen auth0 because it is a popular service that is easy to configure, but any other token issuer capable of issuing custom JWT tokens would work as well.
- For the application: A simple single page web application that uses an OAuth based workflow to securely acquire access tokens from auth0.com, and then uses the tokens in the Daml JSON API.
To simplify user management, we’ll set up a “public” ledger: everyone can sign up to use the ledger, and a new Daml party is automatically allocated for each new user.
The authentication workflow looks like this: a web application contacts auth0.com and asks for a ledger API access token (1). The user chooses to log in via google, and auth0.com then asks google to authenticate the user (2). Google asks the user for consent (“are you ok with sharing your identity with this application?”) (3, 4), and returns a google ID token (signed by google) to auth0.com (5). This token proves the identity of the user, and contains among others the verified email address of the user. Auth0.com uses this information to automatically look up or allocate a Daml party on the ledger (6), generates a corresponding [ledger API access token (signed by auth0.com), and sends it to the application (7). The application then attaches this token to each ledger API request (8). Finally, the ledger verifies the cryptographic signature of the token and checks whether the token authorizes the user to perform the given ledger API operation.
The remainder of this blog post will provide a step by step explanation of how to set up auth0, the web app, and the sandbox. If you wish to just explore the example application, the source code is available here. We recommend that you change any auth0 configuration values in the example code.
To start, log in to auth0.com or create a new auth0.com account.
Then, in the auth0.com web UI, create a new application of type “single page application”. Select your new application, go to its quick start tab, select React as your frontend technology, and download the sample code. On the same page, follow the instructions in the “Configure Auth0” section. If you now run the application (following the instructions in its readme file), you should already be able to log in to your application using google.
Next, we need to tell auth0 about our ledger API. In the auth0 web UI, create a new API. Call it “Ledger API” and use “#” as the API identifier. If you pick a different API identifier, adjust the “audience” parameter in the web app accordingly, as described in the next section.
Create a new rule in the auth0 web UI with the following content, and call it “Onboard user to the ledger”. This rule only runs once for each user, and stores the Daml party identifier in the auth0 user metadata.
Then, create another rule with the following content, and call it “Add ledger API claims”. This rule runs every time a user requests an access token, and extends the default token with claims that are specific for the sandbox ledger API. Use the rule configuration object if you do not want to hardcode the ledger and application ID in the script
Requesting access tokens
In order to work with an authenticated ledger, we need to modify our web application to request access tokens. Open auth_config.json and add an “audience” field.
Then, open index.js and add an “audience” parameter to the Auth0Provider react element.
Now add the following component to your application. The main function callLedgerApi() consists of two important steps:
- Calling getTokenSilently(). This function provided by the auth0 client library asynchronously returns the ledger API access token.
- Calling fetch(). This is a regular REST call to the Daml JSON API, but with an additional “Authorization” HTTP header where we place the access token.
Starting the web app
The Daml JSON API does not support CORS. In order to run your application locally, add a proxy field to your package.json file. The create react app script will use this to route all unknown requests to the given address.
You can now start a local development server that hosts the web app using
yarn install & yarn start
Starting the sandbox
We need to tell the Daml sandbox how to verify access tokens. This is done using JWKS, which is a public URL that contains the RSA public key for verifying the cryptographic signature of the access token. The JWKS URL of your auth0 tenant is listed in the auth0 docs after you log in - it should look like
https://YOUR_DOMAIN/.well-known/jwks.json and you should be able to open the URL in your browser.
We also need to specify the sandbox ledger ID, as the tokens generated by our “Add ledger API claims” auth0 rule contain a hardcoded ledger ID.
Putting this together, start the sandbox using the following command:
daml sandbox .daml/dist/quickstart-0.0.1.dar --auth-jwt-rs256-jwks https://YOUR_DOMAIN/.well-known/jwks.json --ledgerId daml-auth0-example-ledger
Starting the JSON API
The last piece in our setup is the HTTP JSON API. Before we can start it, we need to get an access token that will allow the JSON API to list all Daml packages on the ledger. In a real setup, we could set up a process to automatically acquire the token through machine-to-machine authorization. For this example, the easiest way is to log in to our web app, use the new component that uses the ledger API, and copy the user access token from the browser log. Add a “Bearer “ prefix to the token string and save the result into a local file access-token.txt. You can now start the JSON API using
daml json-api --ledger-host localhost --ledger-port 6865 --http-port 4000 --access-token-file ./access-token.txt
Having started the sandbox, the JSON API server, and the dev server hosting the web application, you should now be able to log in to your web app using your social ID, and see responses from the JSON API.
In the above setup, everyone can sign up to use the ledger, and every confirmed email address is assigned a new Daml party. Depending on the application, a more sophisticated setup will be required to link authenticated users to confirmed natural or legal entities.