> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-quickstart-revamp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Learn the differences between using the Auth0 data store and an external database to store user data.

# User Data Storage

Auth0 stores user information for your tenant in a hosted cloud database, or you can choose to store user data in your own custom external database.

To store user data beyond the basic information Auth0 uses for authentication, you can use the Auth0 data store or a custom database. However, if you use the additional data for authentication purposes, we recommend that you use the Auth0 data store, as this allows you to manage your user data through the [Auth0 Management Dashboard](https://manage.auth0.com/#).

## External database vs. Auth0 data store

The Auth0 data store is customized for authentication data. Storing anything beyond the default user information should be done only in limited cases. Here's why:

* **Scalability**: The Auth0 data store is limited in scalability, and your Application's data may exceed the appropriate limits. By using an external database, you keep your Auth0 data store simple, while the more efficient external database contains the extra data;
* **Performance**: Your authentication data is likely accessed at lower frequencies than your other data. The Auth0 data store isn't optimized for high-frequency use, so you should store data that needs to be retrieved more often elsewhere;
* **Flexibility**: Because the Auth0 data store was built to accommodate only user profiles and their associated metadata, you are limited in terms of the actions you can perform on the database. By using separate databases for your other data, you can manage your data as appropriate and perform direct calls without use of Auth0's <Tooltip tip="Management API: A product to allow customers to perform administrative tasks." cta="View Glossary" href="/docs/glossary?term=Management+API">Management API</Tooltip>.

When outsourcing user authentication, there is usually no need to maintain your own users/passwords table. Even so, you may still want to associate application data with authenticated users.

* For example, you could have a Users table that lists each user authenticated by Auth0. Every time a user logs in, you could search the table for that user. If the user does not exist, you would create a new record. If they do exist, you would update all fields, essentially keeping a local copy of all user data.
* Alternatively, you could store the user identifier in each table/collection that has user-associated data. This is a simpler implementation suited to smaller applications.

## User data storage example scenario

Auth0 provides a sample app, a mobile music application, that reflects the end-to-end user experience when using Auth0 with a custom external database. The sample app is an iOS app created using the [Auth0 iOS seed project](/docs/quickstart/native/ios-swift). The backend uses the [Node.js API](/docs/quickstart/backend/nodejs).

For a visualization of the application's overall structure, see the [Mobile + API architecture scenario](/docs/get-started/architecture-scenarios/mobile-api).

### Metadata

#### App metadata

The following data points from our mobile music application are appropriate to store in `app_metadata`:

* User's subscription plan
* User's right (or lack thereof) to edit featured playlists

These two data points should be stored in `app_metadata` instead of `user_metadata` because they should not be directly changeable by the user.

#### User metadata

The following data points from our mobile music application are appropriate to store in `user_metadata`:

* Application preferences
* Details chosen by the user to alter their experience of the app upon login.

Note that, unlike the data points for `app_metadata`, the user can easily and readily change those stored in `user_metadata`.

We can let the user change their `displayName`, which is the name the user sees upon logging in and is displayed to other users of the app.

To display the user's chosen identifier whenever they log in, we use a rule to get the `user.user_metadata` value.

```javascript lines
function(user, context, callback){
  user.user_metadata = user.user_metadata || {};
  user.user_metadata.displayName = user.user_metadata.displayName || "user";

  auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
    .then(function(){
      callback(null, user, context);
    })
    .catch(function(err){
      callback(err);
    });
}
```

Here's a look at the screen the user would use to change their `displayName`:

<Frame>
  <img src="https://mintcdn.com/docs-staging-quickstart-revamp/MuTsjoV4fPPSGZz9/images/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png?fit=max&auto=format&n=MuTsjoV4fPPSGZz9&q=85&s=caa472e8bd2a36513d8b901db605dff1" alt="iOS app settings screen with option to update display name." width="300" height="534" data-path="images/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png" />
</Frame>

To save the changes to the database, the application makes a call to the [Get a User](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id) endpoint of the Management API to identify the appropriate user:

<CodeGroup>
  ```bash cURL lines
  curl --request GET \
    --url https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id \
    --header 'authorization: Bearer {yourIdToken}'
  ```

  ```csharp C# lines
  var client = new RestClient("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id");
  var request = new RestRequest(Method.GET);
  request.AddHeader("authorization", "Bearer {yourIdToken}");
  IRestResponse response = client.Execute(request);
  ```

  ```go Go lines
  package main

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

  func main() {

  	url := "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id"

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

  	req.Header.Add("authorization", "Bearer {yourIdToken}")

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

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

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

  }
  ```

  ```java Java lines
  HttpResponse<String> response = Unirest.get("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")
    .header("authorization", "Bearer {yourIdToken}")
    .asString();
  ```

  ```javascript Node.JS lines
  var axios = require("axios").default;

  var options = {
    method: 'GET',
    url: 'https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id',
    headers: {authorization: 'Bearer {yourIdToken}'}
  };

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

  ```obj-c Obj-C lines
  #import <Foundation/Foundation.h>

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

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id"]
                                                         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];
  ```

  ```php PHP lines
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://%7ByourAccount%7D.auth0.com/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 => "GET",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer {yourIdToken}"
    ],
  ]);

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

  curl_close($curl);

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

  ```python Python lines
  import http.client

  conn = http.client.HTTPSConnection("")

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

  conn.request("GET", "%7ByourAccount%7D.auth0.com/api/v2/users/user_id", headers=headers)

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

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

  ```ruby Ruby lines
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://%7ByourAccount%7D.auth0.com/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::Get.new(url)
  request["authorization"] = 'Bearer {yourIdToken}'

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

  ```swift Swift lines
  import Foundation

  let headers = ["authorization": "Bearer {yourIdToken}"]

  let request = NSMutableURLRequest(url: NSURL(string: "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")! as URL,
                                          cachePolicy: .useProtocolCachePolicy,
                                      timeoutInterval: 10.0)
  request.httpMethod = "GET"
  request.allHTTPHeaderFields = headers

  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()
  ```
</CodeGroup>

This is followed by a call to the [Update a User](https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id) endpoint to update the `user_metadata` field:

<CodeGroup>
  ```bash cURL lines
  curl --request PATCH \
    --url 'https://{yourDomain}/api/v2/users/user_id' \
    --header 'authorization: Bearer {yourAccessToken}' \
    --header 'content-type: application/json' \
    --data '{"user_metadata": {"displayName": "J-vald3z"}'
  ```

  ```csharp C# lines
  var client = new RestClient("https://{yourDomain}/api/v2/users/user_id");
  var request = new RestRequest(Method.PATCH);
  request.AddHeader("authorization", "Bearer {yourAccessToken}");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go lines expandable
  package main

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

  func main() {

  	url := "https://{yourDomain}/api/v2/users/user_id"

  	payload := strings.NewReader("{\"user_metadata\": {\"displayName\": \"J-vald3z\"}")

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

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

  }
  ```

  ```java Java lines
  HttpResponse<String> response = Unirest.patch("https://{yourDomain}/api/v2/users/user_id")
    .header("authorization", "Bearer {yourAccessToken}")
    .header("content-type", "application/json")
    .body("{\"user_metadata\": {\"displayName\": \"J-vald3z\"}")
    .asString();
  ```

  ```javascript Node.JS lines
  var axios = require("axios").default;

  var options = {
    method: 'PATCH',
    url: 'https://{yourDomain}/api/v2/users/user_id',
    headers: {authorization: 'Bearer {yourAccessToken}', 'content-type': 'application/json'},
    data: '{"user_metadata": {"displayName": "J-vald3z"}'
  };

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

  ```obj-c Obj-C lines
  #import <Foundation/Foundation.h>

  NSDictionary *headers = @{ @"authorization": @"Bearer {yourAccessToken}",
                             @"content-type": @"application/json" };

  NSData *postData = [[NSData alloc] initWithData:[@"{"user_metadata": {"displayName": "J-vald3z"}" dataUsingEncoding:NSUTF8StringEncoding]];

  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/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];
  ```

  ```php PHP lines expandable
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/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\": {\"displayName\": \"J-vald3z\"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer {yourAccessToken}",
      "content-type: application/json"
    ],
  ]);

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

  curl_close($curl);

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

  ```python Python lines
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}"

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

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

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

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

  ```ruby Ruby lines
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/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 {yourAccessToken}'
  request["content-type"] = 'application/json'
  request.body = "{\"user_metadata\": {\"displayName\": \"J-vald3z\"}"

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

  ```swift Swift lines expandable
  import Foundation

  let headers = [
    "authorization": "Bearer {yourAccessToken}",
    "content-type": "application/json"
  ]

  let postData = NSData(data: "{"user_metadata": {"displayName": "J-vald3z"}".data(using: String.Encoding.utf8)!)

  let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/api/v2/users/user_id")! 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()
  ```
</CodeGroup>

You must replace `{yourAccessToken}` with a [Management API Access Token](https://auth0.com/docs/api/management/v2/concepts/tokens).

### User data permission rules

Use [rules](/docs/customize/rules) to implement permissions on whether a user can edit featured playlists or not.

#### Assign Playlist Editor role

The first rule sends a request to our Node API, which then queries the database connected to Heroku to check how many plays the user’s playlist has. If the number is 100 or greater, we assign `playlist_editor` as a value in the `roles` array in `app_metadata`.

```javascript lines expandable
function (user, context, callback) {

  var request = require('request');

  user.app_metadata = user.app_metadata || {};
  user.app_metadata.roles = user.roles || [];

  var CLIENT_SECRET = configuration.AUTH0_CLIENT_SECRET;
  var CLIENT_ID = configuration.AUTH0_CLIENT_ID;

  var scope = {
    user_id: user.user_id,
    email: user.email,
    name: user.name
  };

  var options = {
    subject: user.user_id,
    expiresInMinutes: 600,
    audience: CLIENT_ID,
    issuer: 'https://example.auth0.com'
  };

  var id_token = jwt.sign(scope, CLIENT_SECRET, options);

  var auth = 'Bearer ' + id_token;

  request.get({
    url: 'https://example.com/playlists/getPlays',
    headers: {
       'Authorization': auth,
      'Content-Type': 'text/html'
    },
    timeout: 15000
  }, function(err, response, body){
    if (err)
      return callback(new Error(err));
    var plays = parseInt(body, 10);

    if (plays >= 100 && user.roles.indexOf('playlist_editor') < 0){
      user.app_metadata.roles.push('playlist_editor');
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }

    else if (plays < 100 && user.roles.indexOf('playlist_editor') >= 0){
      user.app_metadata.roles = [];
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }
    else{
      callback(null, user, context);
    }

  });

}
```

#### Scope parameter specifies role

The second rule gets the `app_metadata` field and assigns the `roles` array to a field in the user object so it can be accessed without calling `app_metadata` on the application. The `scope` parameter can then specify `roles` upon the user logging in without including everything in `app_metadata` in the user object:

```javascript lines
function(user, context, callback) {
   if (user.app_metadata) {
      user.roles = user.app_metadata.roles;
   }
   user.roles = user.roles || [];
   callback(null, user, context);
}
```

After we've implemented these two rules, the app recognizes whether the user is a playlist editor or not and changes the welcome screen accordingly. If `playlist_editor` is in the `roles` array stored in the user's `app_metadata`, the user will be welcomed as an **EDITOR** after signing in:

<Frame>
  <img src="https://mintcdn.com/docs-staging-quickstart-revamp/AmyDKn5Na0kFZtbL/images/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png?fit=max&auto=format&n=AmyDKn5Na0kFZtbL&q=85&s=589b5fbf9bec6e84cae89721de45df33" alt="Example of user profile page with editor role." width="300" height="534" data-path="images/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png" />
</Frame>

#### Associate a user's music with the user

We need to associate a user's music with that user, but this information is not required for authentication. Here's how to store this information in a separate database that is integrated with the backend of the application.

The user's unique identifier is the `user_id` and it's the sub-property of the `idTokenPayload` object in an `authResult`. Here is a sample row from the `songs` table in our database:

<table class="table">
  <thead>
    <tr>
      <th>song\_id</th>
      <th>songname</th>
      <th>user\_id</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>1</td>
      <td>Number One Hit</td>
      <td>google-oauth2</td>
    </tr>
  </tbody>
</table>

The Node.js backend authenticates requests to the URI associated with getting the user’s personal data from the database by validating a <Tooltip tip="JSON Web Token (JWT): Standard ID Token format (and often Access Token format) used to represent claims securely between two parties." cta="View Glossary" href="/docs/glossary?term=JSON+Web+Token">JSON Web Token</Tooltip>.

[Learn about token-based authentication and how to implement JWT in your Applications.](/docs/secure/tokens/json-web-tokens)

Here is the code implementing JWT validation from the Node.js seed project:

```javascript lines
var genres = require('./routes/genres');
var songs = require('./routes/songs');
var playlists = require('./routes/playlists');
var displayName = require('./routes/displayName');

var authenticate = jwt({
  secret: process.env.AUTH0_CLIENT_SECRET,
  audience: process.env.AUTH0_CLIENT_ID
});

app.use('/genres', authenticate, genres);
app.use('/songs', authenticate, songs);
app.use('/playlists', authenticate, playlists);
app.use('/displayName', authenticate, displayName);
```

We can add functionality to handle different data requests from our Application. For example, if we receive a `GET` request to `/secured/getFavGenre`, the API calls the `queryGenre()` function, which queries the database for and responds with the user’s favorite genre.

```swift lines
@IBAction func getGenre(sender: AnyObject) {
        let request = buildAPIRequest("/genres/getFav", type:"GET")
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {[unowned self](data, response, error) in
            let genre = NSString(data: data!, encoding: NSUTF8StringEncoding)
            dispatch_async(dispatch_get_main_queue(), {
                self.favGenre.text = "Favorite Genre:  \(genre!)"
            })
        }
        task.resume()
    }
```

The function `buildAPIRequest()` takes the path and HTTP method of the request as parameters and builds a request using the base URL of our Node.js API that's hosted on Heroku.

In the Application, the `getGenre()` function makes a request to the API and changes the app's interface to display the request response to `/genres/getFav`. The backend retrieves the required data for this action using the `queryGenre()` function and returns the results to the Application:

```javascript lines
function queryGenre(user_id, res){

  db.connect(process.env.DATABASE_URL, function(err, client) {
  if (err) throw err;

  client
    .query('SELECT fav_genre as value FROM user_data WHERE user_id = $1', [user_id], function(err, result) {

      if(err) {
        return console.error('error running query', err);
      }
      res.send(result.rows[0].value);
    });
  });

};
```

## Learn more

* [Normalized User Profiles](/docs/manage-users/user-accounts/user-profiles/normalized-user-profiles)
* [Normalized User Profile Schema](/docs/manage-users/user-accounts/user-profiles/normalized-user-profile-schema)
* [Sample User Profiles](/docs/manage-users/user-accounts/user-profiles/sample-user-profiles)
