Configure and Implement Multi-Resource Refresh Token

Configure applications for MRRT

To use Multi-Resource Refresh Tokens (MRRT), configure your application’s refresh token policies using the Auth0 Management API. These policies will specify which API and scopes the application is allowed to request during a refresh token exchange.

You can define MRRT policies in the refresh_token.policies property of the application.

Property Type Description
audience string The Auth0 API identifier of the application that will have access to using the refresh token.
scope Array of strings The list of scopes allowed when requesting an access token for the specified audience. The scope must be equal to or narrower than the scopes defined on the API.

For existing applications, make a PATCH call to the Update a Client endpoint. To create a new application, make a POST call to the Create a Client endpoint:


curl --request PATCH \
  --url 'https://{yourDomain}/api/v2/clients//{yourClientId}' \
  --header 'authorization: Bearer {yourMgmtApiAccessToken}' \
  --header 'content-type: application/json' \
  --data '{
  "refresh_token": {
    "expiration_type": "expiring",
    "rotation_type": "rotating",
    "token_lifetime": 31557600,
    "idle_token_lifetime": 2592000,
    "leeway": 0,
    "infinite_token_lifetime": false,
    "infinite_idle_token_lifetime": false,
    "policies": [
      {
        "audience": "https://api.example.com",
        "scope": ["read:data"]
      },
      {
        "audience": "https://billing.example.com",
        "scope": ["read:billing"]
      }
    ]
  }
}'

Was this helpful?

/
var client = new RestClient("https://{yourDomain}/api/v2/clients//{yourClientId}");
var request = new RestRequest(Method.PATCH);
request.AddHeader("authorization", "Bearer {yourMgmtApiAccessToken}");
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

Was this helpful?

/
package main

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

func main() {

	url := "https://{yourDomain}/api/v2/clients//{yourClientId}"

	payload := strings.NewReader("{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}")

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

	req.Header.Add("authorization", "Bearer {yourMgmtApiAccessToken}")
	req.Header.Add("content-type", "application/json")

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

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

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

}

Was this helpful?

/
HttpResponse<String> response = Unirest.patch("https://{yourDomain}/api/v2/clients//{yourClientId}")
  .header("authorization", "Bearer {yourMgmtApiAccessToken}")
  .header("content-type", "application/json")
  .body("{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}")
  .asString();

Was this helpful?

/
var axios = require("axios").default;

var options = {
  method: 'PATCH',
  url: 'https://{yourDomain}/api/v2/clients//{yourClientId}',
  headers: {
    authorization: 'Bearer {yourMgmtApiAccessToken}',
    'content-type': 'application/json'
  },
  data: {
    refresh_token: {
      expiration_type: 'expiring',
      rotation_type: 'rotating',
      token_lifetime: 31557600,
      idle_token_lifetime: 2592000,
      leeway: 0,
      infinite_token_lifetime: false,
      infinite_idle_token_lifetime: false,
      policies: [
        {audience: 'https://api.example.com', scope: ['read:data']},
        {audience: 'https://billing.example.com', scope: ['read:billing']}
      ]
    }
  }
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});

Was this helpful?

/
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer {yourMgmtApiAccessToken}",
                           @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"refresh_token": @{ @"expiration_type": @"expiring", @"rotation_type": @"rotating", @"token_lifetime": @31557600, @"idle_token_lifetime": @2592000, @"leeway": @0, @"infinite_token_lifetime": @NO, @"infinite_idle_token_lifetime": @NO, @"policies": @[ @{ @"audience": @"https://api.example.com", @"scope": @[ @"read:data" ] }, @{ @"audience": @"https://billing.example.com", @"scope": @[ @"read:billing" ] } ] } };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/api/v2/clients//{yourClientId}"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"PATCH"];
[request setAllHTTPHeaderFields:headers];
[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];

Was this helpful?

/
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://{yourDomain}/api/v2/clients//{yourClientId}",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_POSTFIELDS => "{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer {yourMgmtApiAccessToken}",
    "content-type: application/json"
  ],
]);

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

curl_close($curl);

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

Was this helpful?

/
import http.client

conn = http.client.HTTPSConnection("")

payload = "{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}"

headers = {
    'authorization': "Bearer {yourMgmtApiAccessToken}",
    'content-type': "application/json"
    }

conn.request("PATCH", "/{yourDomain}/api/v2/clients//{yourClientId}", payload, headers)

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

print(data.decode("utf-8"))

Was this helpful?

/
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://{yourDomain}/api/v2/clients//{yourClientId}")

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

request = Net::HTTP::Patch.new(url)
request["authorization"] = 'Bearer {yourMgmtApiAccessToken}'
request["content-type"] = 'application/json'
request.body = "{\n  \"refresh_token\": {\n    \"expiration_type\": \"expiring\",\n    \"rotation_type\": \"rotating\",\n    \"token_lifetime\": 31557600,\n    \"idle_token_lifetime\": 2592000,\n    \"leeway\": 0,\n    \"infinite_token_lifetime\": false,\n    \"infinite_idle_token_lifetime\": false,\n    \"policies\": [\n      {\n        \"audience\": \"https://api.example.com\",\n        \"scope\": [\"read:data\"]\n      },\n      {\n        \"audience\": \"https://billing.example.com\",\n        \"scope\": [\"read:billing\"]\n      }\n    ]\n  }\n}"

response = http.request(request)
puts response.read_body

Was this helpful?

/
import Foundation

let headers = [
  "authorization": "Bearer {yourMgmtApiAccessToken}",
  "content-type": "application/json"
]
let parameters = ["refresh_token": [
    "expiration_type": "expiring",
    "rotation_type": "rotating",
    "token_lifetime": 31557600,
    "idle_token_lifetime": 2592000,
    "leeway": 0,
    "infinite_token_lifetime": false,
    "infinite_idle_token_lifetime": false,
    "policies": [
      [
        "audience": "https://api.example.com",
        "scope": ["read:data"]
      ],
      [
        "audience": "https://billing.example.com",
        "scope": ["read:billing"]
      ]
    ]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/api/v2/clients//{yourClientId}")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Was this helpful?

/

Sample response:

{
  "client_id": "abc123xyz",
  "name": "My Native App",
  "refresh_token": {
    "rotation_type": "rotating",
    "policies": [
      {
        "audience": "https://api.example.com",
        "scope": ["read:data"]
      },
      {
        "audience": "https://billing.example.com",
        "scope": ["read:billing"]
      }
    ]
  }
}

Was this helpful?

/

Implement multi-resource refresh token

Once you configure your application’s refresh token with MRRT policies, you can start to  exchange a single refresh token for access tokens across multiple APIs. To facilitate this, your application needs to initiate a login flow using either the Authorization Code Flow or the Resource Owner Password Grant.

Step 1: Authenticate and request a refresh token

To receive a refresh token, include the offline_access scope when initiating the authentication request. To learn more, read Get Refresh Tokens.

Step 2: Exchange the refresh token for a different API

Once the refresh token is issued, you can request access tokens for any API and scopes defined in both the initial authentication and the MRRT policy. For example:


curl --request POST \
  --url 'https://{yourDomain}/oauth/token' \
  --header 'content-type: application/json' \
  --data '{
  "grant_type": "refresh_token",
  "client_id": "${clientId}",
  "refresh_token": "${refreshToken}",
  "audience": "https://billing.example.com",
  "scope": "read:billing write:billing"
}'

Was this helpful?

/
var client = new RestClient("https://{yourDomain}/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

Was this helpful?

/
package main

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

func main() {

	url := "https://{yourDomain}/oauth/token"

	payload := strings.NewReader("{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}")

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

	req.Header.Add("content-type", "application/json")

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

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

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

}

Was this helpful?

/
HttpResponse<String> response = Unirest.post("https://{yourDomain}/oauth/token")
  .header("content-type", "application/json")
  .body("{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}")
  .asString();

Was this helpful?

/
var axios = require("axios").default;

var options = {
  method: 'POST',
  url: 'https://{yourDomain}/oauth/token',
  headers: {'content-type': 'application/json'},
  data: {
    grant_type: 'refresh_token',
    client_id: '${clientId}',
    refresh_token: '${refreshToken}',
    audience: 'https://billing.example.com',
    scope: 'read:billing write:billing'
  }
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});

Was this helpful?

/
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"grant_type": @"refresh_token",
                              @"client_id": @"${clientId}",
                              @"refresh_token": @"${refreshToken}",
                              @"audience": @"https://billing.example.com",
                              @"scope": @"read:billing write:billing" };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/oauth/token"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[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];

Was this helpful?

/
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://{yourDomain}/oauth/token",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}",
  CURLOPT_HTTPHEADER => [
    "content-type: application/json"
  ],
]);

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

curl_close($curl);

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

Was this helpful?

/
import http.client

conn = http.client.HTTPSConnection("")

payload = "{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}"

headers = { 'content-type': "application/json" }

conn.request("POST", "/{yourDomain}/oauth/token", payload, headers)

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

print(data.decode("utf-8"))

Was this helpful?

/
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://{yourDomain}/oauth/token")

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["content-type"] = 'application/json'
request.body = "{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"${clientId}\",\n  \"refresh_token\": \"${refreshToken}\",\n  \"audience\": \"https://billing.example.com\",\n  \"scope\": \"read:billing write:billing\"\n}"

response = http.request(request)
puts response.read_body

Was this helpful?

/
import Foundation

let headers = ["content-type": "application/json"]
let parameters = [
  "grant_type": "refresh_token",
  "client_id": "${clientId}",
  "refresh_token": "${refreshToken}",
  "audience": "https://billing.example.com",
  "scope": "read:billing write:billing"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/oauth/token")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Was this helpful?

/

 To learn more, read Use Refresh tokens.

Step 3: Call the API using the access token

Use the access token to call the secured API using the Bearer HTTP authorization scheme. To learn more, read Use Access Tokens.

Use Multi-resource refresh token with Actions

Using MRRT with Actions allows you to configure dynamic decision-making based on the application’s MRRT policies. To facilitate this, post-login Actions features the event.client.refresh_token.policies object that provides relevant information including audience and scope. 

You can use the event.client.refresh_token.policies object to evaluate the application's audience and scope, when issuing or exchanging a refresh token, and to ensure precise control over API access and scopes.

exports.onExecutePostLogin = async (event, api) => {
  // return the list of allowed APIs in the client
  const allowedAPIsInTheClient = event.client.refresh_token?.policies;

  if(allowedAPIsInTheClient?.some(policy => policy?.audience?.includes('https://myapi'))){
    // custom logic
  }
};

Was this helpful?

/

Evaluation logic

MRRT acts as an extension of the original authentication, not a replacement. When exchanging a refresh token, Auth0 evaluates the exchange request using the following logic:

  • If the audience parameter is omitted, Auth0 returns an access token with the original audience and any of its additional scopes configured in the MRRT policy.

  • If a new audience parameter is specified, Auth0 verifies that the audience is included in the MRRT policy and returns an access token for the new audience with its configured scopes.

  • If the scope parameter is omitted, Auth0 combines all allowed scopes from the original request and the MRRT policy.

  • If a new scope parameter is specified, Auth0 validates the requested scopes and returns an access token with the scopes included in the MRTT policy. Invalid or unauthorized scopes requested are silently ignored.

  • If the audience parameter is the same as from the original request, Auth0 applies the MRRT policy and returns an access token for the audience with all MRRT configured scopes and original authentication scopes.

MRRT allows you to extend user access to new APIs without issuing new refresh tokens or requiring user login again.

Examples

A user logs in requesting the following audience and scope:

{
"audience": "https://api.example.com",  
"scope": "openid profile read:messages"
}

Was this helpful?

/

The application’s MRRT policy is configured to add an additional scope:

{
  "audience": "https://api.example.com",
  "scope": ["write:messages"]
}

Was this helpful?

/

A refresh token exchange using the same audience and no scopes would result in an access token containing all configured scopes:

{
"aud": "https://api.example.com"  
"scope": "openid profile read:messages write:messages"
}

Was this helpful?

/