Call AWS APIs and Resources Securely with Tokens

As of 8 June 2017, new Auth0 customers cannot add any of the legacy grant types to their clients, which are required for use with the Delegation endpoint. Legacy grant types are only available for previous customers while they migrate to new flows, to avoid breaking changes. To find the secure alternative for your case refer to Secure Alternatives to the Legacy Grant Types. If you have any questions about which alternative you should use, please contact Support.

Auth0 integrates with the AWS Security Token Service (STS) to obtain an limited-privilege credentials for AWS Identity and Access Management (IAM) users or for users that you authenticate (federated users). These credentials can then be used to call the AWS API of any Auth0-supported identity provider.

Sample Configuration

  1. The web app authenticates its users via Social providers, such as Facebook, LinkedIn, or Twitter, or corporate credentials, such as Active Directory, Azure Active Directory, or Salesforce.
  2. The app calls Auth0's delegation endpoint to request a token for use with AWS.
  3. Auth0 obtains the token from AWS on behalf of the app.
  4. The app uses the newly-obtained token to connect with any AWS API.

Set Up Delegation

For detailed instructions on configuring delegation, see How to Set Up AWS for Delegated Authentication.

Log in to Auth0's Management Dashboard, navigate to the Clients area, and find the client associated with your app. Click on Settings and click over to the Addons tab. Enable the Amazon Web Services addon.

Username Length with AWS

Users of Auth0's database or a custom database should note that AWS usernames must be between 2-64 characters in length. If you're using an Auth0 database, you can enforce this by setting your username length settings accordingly. If you're using a custom database, you can implement a similar policy within your application.

IAM policy

The following is a sample AWS IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Action": [
              "s3:DelObject",
              "s3:GetObject",
              "s3:PutObject",
              "s3:PutObjectAcl"
          ],
          "Resource": [
              "arn:aws:s3:::YOUR_BUCKET_NAME/${saml:sub}/*"
          ],
          "Effect": "Allow"
      }
  ]
}

The IAM policy is a dynamic policy that gives access to a folder in a bucket. The folder name is set based on an attribute of the digitally-signed SAML token that Auth0 exchanges with AWS on your behalf.

The ${saml:sub} will be automatically mapped from the authenticated user (sub means subject and is equal to the user identifier), which allows the original identity of the user to be used throughout your app and AWS.

Get the AWS Token for an Authenticated User

When a user successfully authenticates, Auth0 returns an id_token, which is a JWT). This id_token is then used to request an Auth0 and AWS token using the delegation endpoint.

Here is a sample request on the delegation endpoint:


curl --request POST \
  --url 'https://YOUR_AUTH0_DOMAIN/delegation' \
  --data '{ "client_id": "YOUR_CLIENT_ID", "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "id_token": "YOUR_ID_TOKEN", "target": "YOUR_CLIENT_ID", "api_type": "aws" }'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/delegation");
var request = new RestRequest(Method.POST);
request.AddParameter("undefined", "{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/delegation"

	payload := strings.NewReader("{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }")

	req, _ := http.NewRequest("POST", url, payload)

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://YOUR_AUTH0_DOMAIN/delegation")
  .body("{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/delegation",
  "method": "POST",
  "headers": {},
  "processData": false,
  "data": "{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }"
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require("request");

var options = { method: 'POST',
  url: 'https://YOUR_AUTH0_DOMAIN/delegation',
  body: 
   { client_id: 'YOUR_CLIENT_ID',
     grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
     id_token: 'YOUR_ID_TOKEN',
     target: 'YOUR_CLIENT_ID',
     api_type: 'aws' },
  json: true };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});
#import <Foundation/Foundation.h>
NSDictionary *parameters = @{ @"client_id": @"YOUR_CLIENT_ID",
                              @"grant_type": @"urn:ietf:params:oauth:grant-type:jwt-bearer",
                              @"id_token": @"YOUR_ID_TOKEN",
                              @"target": @"YOUR_CLIENT_ID",
                              @"api_type": @"aws" };

NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/delegation"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/delegation",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }",
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }"

conn.request("POST", "/YOUR_AUTH0_DOMAIN/delegation", payload)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'

url = URI("https://YOUR_AUTH0_DOMAIN/delegation")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(url)
request.body = "{ \"client_id\": \"YOUR_CLIENT_ID\", \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\", \"id_token\": \"YOUR_ID_TOKEN\", \"target\": \"YOUR_CLIENT_ID\", \"api_type\": \"aws\" }"

response = http.request(request)
puts response.read_body
import Foundation
let parameters = [
  "client_id": "YOUR_CLIENT_ID",
  "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  "id_token": "YOUR_ID_TOKEN",
  "target": "YOUR_CLIENT_ID",
  "api_type": "aws"
]

let postData = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/delegation")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "POST"
request.HTTPBody = postData

let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    println(error)
  } else {
    let httpResponse = response as? NSHTTPURLResponse
    println(httpResponse)
  }
})

dataTask.resume()
Parameters Description
client_id The ID of your Auth0 client
grant_type Set as urn:ietf:params:oauth:grant-type:jwt-bearer
id_token The existing ID token for the user requesting access
target The target client's ID
api_type The API the user wants to call (this must be aws)

AWS also requires the role and principal ARN values. You can set these values using rules. The following is a sample rule that you can use. Copy the provider (for use as the principle ARN) and role ARN values, and paste them into the sample where it currently says [omitted]:

function (user, context, callback) {
  if (context.clientID === 'CLIENT_ID' &&
      context.protocol === 'delegation') {
    // set AWS settings
    context.addonConfiguration = context.addonConfiguration || {};
    context.addonConfiguration.aws = context.addonConfiguration.aws || {};
    context.addonConfiguration.aws.principal = 'arn:aws:iam::[omitted]:saml-provider/auth0-provider';
    context.addonConfiguration.aws.role = 'arn:aws:iam::[omitted]:role/auth0-role';
  }

  callback(null, user, context);
}

Optionally, you can set context.addonConfiguration.aws.region to target a specific AWS region. For example, region: 'cn-north-1' will direct requests to the Chinese north region. Temporary credentials from AWS GovCloud (US) and China (Beijing) can be used only in the region from which they originated.

The context.addonConfiguration.aws.mappings variable allows you to specify parameters that are sent to AWS to assume a Role. By default, Auth0 will use these mappings:

    mappings: {
          'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'name',
          'https://aws.amazon.com/SAML/Attributes/Role':                          'awsrole',
          'https://aws.amazon.com/SAML/Attributes/RoleSessionName':               'rolesessionname'
    }

Other mappings are available in AWS, so if you wanted to use the eduPersonAffiliation AWS Context Key, you can set this mapping in a rule as follows:

function(user,context,callback){

    context.addonConfiguration.aws.mappings: {
          'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'name',
          'https://aws.amazon.com/SAML/Attributes/Role':                          'awsrole',
          'https://aws.amazon.com/SAML/Attributes/RoleSessionName':               'rolesessionname',
          'urn:oid:1.3.6.1.4.1.5923.1.1.1.1':                                     'awsGroup'
    };

    callback(null,user,context);
}

The example above assumes the user object contains an awsGroup property with the expected value.

The result of calling the delegation endpoint will contain the AWS token in the Credentials field:

{
  "ResponseMetadata":{
    "RequestId":"ec4cb90f-8a17-11e5-84bf-a9c4083f50c5"
  },
  "Credentials":{
    "AccessKeyId":"ASIAIJGOICAGFNVWAENA",
    "SecretAccessKey":"mRXhJySQHYZVg8...iCh+JeyBQ==",
    "Expiration":"2015-11-13T16:05:05.000Z"
  },
  "AssumedRoleUser":{
    "AssumedRoleId":"AROAID6UVEPILQXDCMMWK:johndoe",
    "Arn":"arn:aws:sts::010616021751:assumed-role/access-to-s3-per-user/johndoe"
  },
  "Subject":"google-oauth2|113015401123457192604",
  "SubjectType":"persistent",
  "Issuer":"urn:matugit.auth0.com",
  "Audience":"https://signin.aws.amazon.com/saml",
  "NameQualifier":"z32keR+u/IrT0MrUVEfqYUGiqvE="
}

Auth0 Libraries

The Auth0 client libraries simplify the process of calling these endpoints. See an example for client-side JavaScript at Delegation Token Request. Please note that this example is for version 7 of the auth0js library; delegation is not supported in version 8 of auth0js.

Additionally, AWS requires two additional parameters: role and principal. To modify the role and principal strings, specify the appropriate ARN values where the sample currently says [omitted] via Rules. If you do not have these values, please see Copy the ARN Values section of the AWS setup doc.

Here is an example of client-side code used to obtain the token:

<script src="https://cdn.auth0.com/w2/auth0-7.6.1.min.js"></script>
<script type="text/javascript">

  var auth0 = new Auth0({
    clientID: 'YOUR_CLIENT_ID',
    domain: 'YOUR_AUTH0_DOMAIN',
    callbackURL: 'dummy'
  });

  var options = {
    id_token: LOGGED_IN_USER_ID_TOKEN,
    api: 'aws',
    role: AWS_ROLE_ARN,
    principal: AWS_SAML_PROVIDER_ARN
  };

  auth0.getDelegationToken(options, function(err,delegationResult){
    if (!err){
      //use delegationResult.Credentials to access AWS API
    }
  });
}
</script>