Docs

Invite-Only Applications

Invite-Only Applications

Many SaaS apps allow self-service provisioning, where users can register themselves and begin using the app. Other types of apps, however, do not allow such signups. Instead, the customer (typically an organization of some type) pay upfront for a number of users, and only the end user with the appropriate credentials may sign up and access the app. In such cases, you can use an invite-only workflow for authorization purposes.

Example Scenario: ExampleCo

In this tutorial, we will work through a sample setup for the fictional company, ExampleCo.

ExampleCo is a multi-tenant SaaS solution offering cloud-based analytics. Customers purchasing licenses send ExampleCo lists of users whom they want to access the application.

You can handle this requirement in Auth0 using an Enterprise Connection (using federation) with the individual customers using ADFS, SAML-P, and so on. This allows the customer to authenticate users with their own Active Directory specifying who gets access to the app.

The invite-only authorization flow includes the following steps:

  1. Creating new users in ExampleCo and bulk importing the same users into Auth0
  2. Triggering the email verification process via Auth0
  3. Triggering the password reset process via Auth0

Setup your Application

You can store all ExampleCo end users in a single database, since everyone will provide their unique corporate email addresses.

To prevent users from signing themselves up and adding themselves to the database connection, be sure to select the Disable Sign Ups option on the connection to make sure users can only be created on the backend.

You will need to create an application in the Dashboard with the correct parameters:

  • Name: give your application a clear name as this will be used in the emails being sent out during the invite-only workflow
  • Application Type: this will be a regular web application.
  • Allowed Callback URLs: this should be the URL of your app

Since this application needs to access the Management API, you'll need to authorize it and set its scopes as follows:

  • Go to the APIs section of the Dashboard.
  • Select Auth0 Management API.
  • Click over to the Machine to Machine Applications tab.
  • Find the application you just created, and set its toggle to Authorized.
  • Use the down arrow to open up the scopes selection area. Select the following scopes: read:users, update:users, delete:users, create:users, and create:user_tickets.
  • Click Update.

Authorize Application

Import Users

Every user that exists in ExampleCo should be created in your Auth0 database connection as well. Auth0 offers a bulk user import functionality for this purpose.

Email Verification

Once you've created the user in Auth0, send a POST request from your app to the Send an email address verification email endpoint that includes:

  • user_id: the Auth0 user ID to send the verification email to.
  • client_id (optional): the client ID of your app.

Be sure to update the MGMT_API_ACCESS_TOKEN placeholder value below with your API Access Token.


curl --request POST \
  --url 'https://YOUR_DOMAIN/api/v2/tickets/email-verification' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN' \
  --data '{ "user_id": "abcd|1234", "client_id": "xyz789" }'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/tickets/email-verification");
var request = new RestRequest(Method.POST);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
request.AddParameter("undefined", "{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_DOMAIN/api/v2/tickets/email-verification"

	payload := strings.NewReader("{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }")

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

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	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_DOMAIN/api/v2/tickets/email-verification")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .body("{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }")
  .asString();
var request = require("request");

var options = {
  method: 'POST',
  url: 'https://YOUR_DOMAIN/api/v2/tickets/email-verification',
  headers: {authorization: 'Bearer MGMT_API_ACCESS_TOKEN'},
  body: {user_id: 'abcd|1234', client_id: 'xyz789'},
  json: true
};

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

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };
NSDictionary *parameters = @{ @"user_id": @"abcd|1234",
                              @"client_id": @"xyz789" };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/tickets/email-verification"]
                                                       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];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/tickets/email-verification",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ),
));

$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 = "{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }"

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("POST", "/YOUR_DOMAIN/api/v2/tickets/email-verification", payload, headers)

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

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

url = URI("https://YOUR_DOMAIN/api/v2/tickets/email-verification")

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["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'
request.body = "{ \"user_id\": \"abcd|1234\", \"client_id\": \"xyz789\" }"

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

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]
let parameters = [
  "user_id": "abcd|1234",
  "client_id": "xyz789"
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/tickets/email-verification")! 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()

Password Reset

Once you've verified the user's email, you will need to initiate the password change process. To do so, your app should make a POST request to Auth0's Management API.

Be sure to replace the placeholder values for your API Access Token, as well as those within the body of the call, including the callback/return URL for your app and the user's details.


curl --request POST \
  --url 'https://YOUR_DOMAIN/api/v2/tickets/password-change' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN' \
  --data '{ "result_url": "YOUR_APP_CALLBACK_URL", "user_id": "USER_ID", "new_password": "secret", "connection_id": "con_0000000000000001", "email": "EMAIL", "ttl_sec": 0 }'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/tickets/password-change");
var request = new RestRequest(Method.POST);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
request.AddParameter("undefined", "{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_DOMAIN/api/v2/tickets/password-change"

	payload := strings.NewReader("{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }")

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

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	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_DOMAIN/api/v2/tickets/password-change")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .body("{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }")
  .asString();
var request = require("request");

var options = {
  method: 'POST',
  url: 'https://YOUR_DOMAIN/api/v2/tickets/password-change',
  headers: {authorization: 'Bearer MGMT_API_ACCESS_TOKEN'},
  body: {
    result_url: 'YOUR_APP_CALLBACK_URL',
    user_id: 'USER_ID',
    new_password: 'secret',
    connection_id: 'con_0000000000000001',
    email: 'EMAIL',
    ttl_sec: 0
  },
  json: true
};

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

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };
NSDictionary *parameters = @{ @"result_url": @"YOUR_APP_CALLBACK_URL",
                              @"user_id": @"USER_ID",
                              @"new_password": @"secret",
                              @"connection_id": @"con_0000000000000001",
                              @"email": @"EMAIL",
                              @"ttl_sec": @0 };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/tickets/password-change"]
                                                       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];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/tickets/password-change",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ),
));

$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 = "{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }"

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("POST", "/YOUR_DOMAIN/api/v2/tickets/password-change", payload, headers)

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

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

url = URI("https://YOUR_DOMAIN/api/v2/tickets/password-change")

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["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'
request.body = "{ \"result_url\": \"YOUR_APP_CALLBACK_URL\", \"user_id\": \"USER_ID\", \"new_password\": \"secret\", \"connection_id\": \"con_0000000000000001\", \"email\": \"EMAIL\", \"ttl_sec\": 0 }"

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

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]
let parameters = [
  "result_url": "YOUR_APP_CALLBACK_URL",
  "user_id": "USER_ID",
  "new_password": "secret",
  "connection_id": "con_0000000000000001",
  "email": "EMAIL",
  "ttl_sec": 0
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/tickets/password-change")! 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()

Summary

This tutorial walked you through implementing an invite-only sign-up flow using the Management API to customize the sign-up process and the email handling.