GDPR: Data Minimization

According to Article 5 of GDPR, the personal data you collect must be limited to what is necessary for processing and must be kept only as long as needed. Appropriate security must be ensured during data processing, including protection against unauthorised or unlawful processing and against accidental loss, destruction, or damage.

There are several Auth0 features than can help you achieve these goals, like account linking, user profile encryption, and more.

The contents of this document are not intended to be legal advice, nor should they be considered a substitute for legal assistance. The final responsibility for understanding and complying with GDPR resides with you, though Auth0 will assist you in meeting GDPR requirements where possible.

Restrict user profile information

To limit the amount of personal information in the Auth0 user profile, you can:

  • Minimize (or avoid) saving personal information in the metadata section of the user profile
  • If you use enterprise directories, configure them to return only the minimum information needed
  • If you use social providers, configure them to return only the minimum information needed
  • Blacklist the user attributes that you do not want to persist in the Auth0 databases

Encrypt user profile information

You can encrypt user information before you save it in the user profile. You can use any encryption mechanism you like prior to storing data in the metadata fields. When a user sets sensitive information, call the Update a user endpoint.

For example, to save the encrypted passportNumber in the user's profile, send this request:


curl --request PATCH \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id' \
  --header 'authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'content-type: application/json' \
  --data '{"user_metadata": {"passportNumber": "B9MuhaDoreVr69MDqx3p8A=="}}'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id");
var request = new RestRequest(Method.PATCH);
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", "Bearer YOUR_ACCESS_TOKEN");
request.AddParameter("application/json", "{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id"

	payload := strings.NewReader("{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}")

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

	req.Header.Add("authorization", "Bearer YOUR_ACCESS_TOKEN")
	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))

}
HttpResponse<String> response = Unirest.patch("https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id")
  .header("authorization", "Bearer YOUR_ACCESS_TOKEN")
  .header("content-type", "application/json")
  .body("{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id",
  "method": "PATCH",
  "headers": {
    "authorization": "Bearer YOUR_ACCESS_TOKEN",
    "content-type": "application/json"
  },
  "processData": false,
  "data": "{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}"
}

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

var options = { method: 'PATCH',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id',
  headers: 
   { 'content-type': 'application/json',
     authorization: 'Bearer YOUR_ACCESS_TOKEN' },
  body: 
   { user_metadata: { passportNumber: 'B9MuhaDoreVr69MDqx3p8A==' } },
  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 YOUR_ACCESS_TOKEN",
                           @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"user_metadata": @{ @"passportNumber": @"B9MuhaDoreVr69MDqx3p8A==" } };

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

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

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_POSTFIELDS => "{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer YOUR_ACCESS_TOKEN",
    "content-type: application/json"
  ),
));

$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_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}"

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

conn.request("PATCH", "/YOUR_AUTH0_DOMAIN/api/v2/users/user_id", payload, headers)

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

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

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id")

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 YOUR_ACCESS_TOKEN'
request["content-type"] = 'application/json'
request.body = "{\"user_metadata\": {\"passportNumber\": \"B9MuhaDoreVr69MDqx3p8A==\"}}"

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

let headers = [
  "authorization": "Bearer YOUR_ACCESS_TOKEN",
  "content-type": "application/json"
]
let parameters = ["user_metadata": ["passportNumber": "B9MuhaDoreVr69MDqx3p8A=="]]

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

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/users/user_id")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "PATCH"
request.allHTTPHeaderFields = headers
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()

Replace the YOUR_ACCESS_TOKEN placeholder with a token that will allow you to access this endpoint. This should be a Management API Token, with the scopes update:users and update:users_app_metadata.

Use account linking

Every time a user uses a connection to log in to your application, a user profile is created if it doeasn't already exist. Note that this is per connection.

To better understand this, consider the following scenario. Your application offers three different options for signup:

  • sign up with email/password
  • login with Google
  • login with Facebook

If a user signs up with Google, this will create a user profile in Auth0. If the same user, upon return, does not remember what he signed up with, and chooses to login with Facebook, Auth0 will create another user profile for the user. So now you have two profiles for the same user.

You can fix this with account linking. You can link multiple accounts under a single user profile, regardless of the connection's type (for example, user/password, social, or SAML).

There are three ways to implement this:

  • Automatic account linking: you can configure a rule that will link accounts with the same email address. For more info and a sample rule, see Automatic Account Linking
  • User-initiated account linking: your app must provide the UI so an authenticated user can link their accounts manually. For a sample implementation, see User Initiated Account Linking
  • Suggested account linking: in this case you still configure a rule that will link accounts with the same verified e-mail address. However, instead of completing the link automatically, your app will first prompt the user to link their identities. For a sample implementation, see Account Linking using server side code

Export logs

You can export Auth0 logs and either store them yourself or automatically push them to external log services. This functionality can help you with data retention requirements, as well as log analysis requirements.

Export logs with the API

You can use the Management API to export logs and store them yourself. There are the two available endpoints, each providing slightly different information.

Search all logs

The Search log events endpoint retrieves log entries that match the search criteria you provided. If you do not provide any search criteria, you will get a list of all available entries.

You can provide search criteria using the q parameter and retrieve specific fields using the fields parameter.

To access the API, you need a Management APIv2 token.

This sample request retrieves all logs for successful logins (the event acronym for successful login is s). The list of fields we will retrieve per log entry is: date, escription, client_id, and log_id.


curl --request GET \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s' \
  --header 'authorization: Bearer YOUR_MGMT_API_ACCESS_TOKEN'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer YOUR_MGMT_API_ACCESS_TOKEN");
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer YOUR_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.get("https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s")
  .header("authorization", "Bearer YOUR_MGMT_API_ACCESS_TOKEN")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s",
  "method": "GET",
  "headers": {
    "authorization": "Bearer YOUR_MGMT_API_ACCESS_TOKEN"
  }
}

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

var options = { method: 'GET',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/logs',
  qs: { fields: 'date,description,client_id,log_id', type: 's' },
  headers: { authorization: 'Bearer YOUR_MGMT_API_ACCESS_TOKEN' } };

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

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

NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_MGMT_API_ACCESS_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

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/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer YOUR_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("")

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

conn.request("GET", "/YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s", headers=headers)

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

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

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s")

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

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer YOUR_MGMT_API_ACCESS_TOKEN'

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

let headers = ["authorization": "Bearer YOUR_MGMT_API_ACCESS_TOKEN"]

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/logs?fields=date%2Cdescription%2Cclient_id%2Clog_id&type=s")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "GET"
request.allHTTPHeaderFields = headers

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()

For details on the search criteria you can use and a list with the event acronyms, see the Search log events endpoint.

Get a single log entry

The Get a log event by ID endpoint retrieves the log entry associated with the provided ID.

This sample request retrieves a single log entry with the ID 90020180129170850881585554625888895190928456277777449010.


curl --request GET \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010' \
  --header 'authorization: Bearer YOUR_MGMT_API_ACCESS_TOKEN'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer YOUR_MGMT_API_ACCESS_TOKEN");
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer YOUR_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.get("https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010")
  .header("authorization", "Bearer YOUR_MGMT_API_ACCESS_TOKEN")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010",
  "method": "GET",
  "headers": {
    "authorization": "Bearer YOUR_MGMT_API_ACCESS_TOKEN"
  }
}

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

var options = { method: 'GET',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010',
  headers: { authorization: 'Bearer YOUR_MGMT_API_ACCESS_TOKEN' } };

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

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

NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_MGMT_API_ACCESS_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

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/api/v2/logs/90020180129170850881585554625888895190928456277777449010",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer YOUR_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("")

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

conn.request("GET", "/YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010", headers=headers)

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

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

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010")

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

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer YOUR_MGMT_API_ACCESS_TOKEN'

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

let headers = ["authorization": "Bearer YOUR_MGMT_API_ACCESS_TOKEN"]

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/logs/90020180129170850881585554625888895190928456277777449010")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "GET"
request.allHTTPHeaderFields = headers

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()

Use Extensions to export to an external service

You can install and configure an Auth0 Extension in order to export logs automatically to another provider, like Sumo Logic or Loggly. For a list of available providers and detailed steps to configure each, see Export Auth0 logs to an external service.

Keep sensitive information from logs

You should minimize any sensitive information contained in URLs that might be captured by Auth0 log files. For example, consider using health-site or similar as your domain name instead of cancer-treatments.


What else do I have to do?

  • Analyze what you are collecting in sign up and through social media and whether that is necessary for the purpose of your service
  • Configure enterprise identity providers to control what data is returned to Auth0
  • Specify what data you want to collect from the social provider and negotiate any particular terms around social login with the social provider around use of the data they will get around your users’ login