Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions main/config/navigation/authenticate.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,33 +281,30 @@
"pages": [
{
"group": "Custom Database Connections",
"directory": "accordion",
"root": "docs/authenticate/database-connections/custom-db",
"pages": [
"docs/authenticate/database-connections/custom-db/overview-custom-db-connections",
"docs/authenticate/database-connections/custom-db/create-db-connection",
"docs/authenticate/database-connections/custom-db/test-custom-database-connections",
"docs/authenticate/database-connections/custom-db/error-handling",
{
"group": "Action Script Templates",
"root": "docs/authenticate/database-connections/custom-db/templates",
"group": "Custom Database Action Scripts",
"root": "docs/authenticate/database-connections/custom-db/custom-database-connections-scripts",
"expanded": true,
"pages": [
"docs/authenticate/database-connections/custom-db/templates/change-password",
"docs/authenticate/database-connections/custom-db/templates/create",
"docs/authenticate/database-connections/custom-db/templates/delete",
"docs/authenticate/database-connections/custom-db/templates/get-user",
"docs/authenticate/database-connections/custom-db/templates/login",
"docs/authenticate/database-connections/custom-db/templates/verify",
"docs/authenticate/database-connections/custom-db/templates/change-email"
"docs/authenticate/database-connections/custom-db/templates/change-email",
"docs/authenticate/database-connections/custom-db/action-script-best-practices"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting, maybe 1 more +tab

]
},
{
"group": "Custom Database and Action Script Best Practices",
"root": "docs/authenticate/database-connections/custom-db/custom-database-connections-scripts",
"group": "Troubleshooting",
"pages": [
"docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/anatomy",
"docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/environment",
"docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/execution",
"docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/connection-security"
"docs/authenticate/database-connections/custom-db/test-custom-database-connections",
"docs/authenticate/database-connections/custom-db/error-handling"
]
}
]
Expand Down
16 changes: 16 additions & 0 deletions main/config/redirects.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
[
{
"source": "/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/anatomy",
"destination": "/docs/manage-users/user-migration/configure-automatic-migration-from-your-database"
},
{
"source": "/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts/*",
"destination": "/docs/authenticate/database-connections/custom-db/action-script-best-practices"
},
{
"source": "/docs/authenticate/database-connections/custom-db/overview-custom-db-connections",
"destination": "/docs/authenticate/database-connections/custom-db"
},
{
"source": "/docs/authenticate/database-connections/custom-db/templates",
"destination": "/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts"
},
{
"source": "/docs/manage-users/user-search/retrieve-users-with-get-users-endpoint",
"destination": "/docs/manage-users/user-search/list-and-search-users"
Expand Down
26 changes: 16 additions & 10 deletions main/docs/authenticate/database-connections/custom-db.mdx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
---
title: Custom Database Connections
description: Learn about authenticating users using your database as an identity provider.
description: Use a custom database connection to connect Auth0 to your external user store for authentication, migration, or multi-tenant architectures.
validatedOn: 2026-02-25
---

Use a custom database connection when you want to provide Auth0 with access to your own independent (legacy) identity data store primarily for authentication (filling the role of an <Tooltip tip="Identity Provider (IdP): Service that stores and manages digital identities." cta="View Glossary" href="/docs/glossary?term=identity+provider">identity provider</Tooltip>) and for migrating user data to Auth0's data store.
Custom database connections let you connect Auth0 to an external user store. This supports use cases like:

Auth0 [Extensibility](/docs/customize/extensions) allows you to add custom logic to build out last-mile solutions for Identity and Access Management (IdAM). Auth0 provides extensibility through [Actions](/docs/customize/actions) and [scripts](/docs/authenticate/database-connections/custom-db/templates) for both custom database connections and custom database migration. Each is implemented using [Node.js](https://nodejs.org/en/) running on the Auth0 platform in an Auth0 tenant.
* **Authenticating users using your own external user store** as the identity provider instead of Auth0's user store.

Auth0 extensibility executes at different points in the IAM pipeline:
In this application, after a user's first successful login authenticating with the external user store, Auth0 creates a [user account](/docs/manage-users/user-accounts) but does not store a hash of their passwords and never authenticates users with the Auth0 user store.

* **Actions** are secure, tenant-specific, versioned functions written in Node.js that execute at certain points within the Auth0 platform. Actions are used to customize and extend Auth0’s capabilities with custom logic. To learn more, read [Understand how Auth0 Actions Work](/docs/customize/actions/actions-overview).
* **Incrementally [migrating users](/docs/manage-users/user-migration/configure-automatic-migration-from-your-database) to Auth0** from your external user store (sometimes called *trickle* or *lazy* migration).

* **Custom database action scripts** can be used to integrate with an existing user identity store, or can be used where [automatic user migration](/docs/manage-users/user-migration) from a legacy identity store is required. Various [templates](/docs/authenticate/database-connections/custom-db/templates) are provided.
In this application, when a user tries to log in, Auth0 always tries to authenticate them with the Auth0 user store first. If the user does not exist there, Auth0 tries to authenticate them with the external user store and, if successful, creates the user in the Auth0 user store with the same ID and password used to log in.

Whatever the use case, Auth0 extensibility allows you to tailor IAM operations to your exact requirements. However, if not used in the right way, this can open up the potential for improper or unintended use which can lead to problematic situations down the line. In an attempt to address matters ahead of time, Auth0 provides [best practice guidance](/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts) to both designers and implementers, and we recommend reading it in its entirety at least once, even if you've already started your journey with Auth0.
* **Proxying access to an Auth0 tenant** in [multi-tenant architecture scenarios](/docs/get-started/auth0-overview/create-tenants/multi-tenant-apps-best-practices).

<Card title="Availability varies by Auth0 plan">
Your Auth0 plan or custom agreement affects whether this feature is available. To learn more, read [Pricing](https://auth0.com/pricing).
</Card>
<Accordion title="See an example infrastructure diagram with Universal Login">
For example, as part of the <Tooltip tip="Universal Login: Your application redirects to Universal Login, hosted on Auth0's Authorization Server, to verify a user's identity." cta="View Glossary" href="/docs/glossary?term=Universal+Login">Universal Login</Tooltip> workflow, custom database connections obtain user identity information from your external user store:

<Frame>![Custom Database Connections Anatomy](/docs/images/cdy7uua7fh8z/2lHqvZKFiEbAXURU2gmchc/626cac94211c266f2135a41456b2e49d/custom-database-connections.png)</Frame>
</Accordion>

You control how Auth0 interfaces with your external user store by writing [database action scripts](/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts), which are Node.js functions that Auth0 calls during functionality like logins and password changes.

You can customize the behavior of database action scripts to suit your specific use case. We provide several templates for each database action script to help you get started.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
title: Best Practices for Database Action Scripts
sidebarTitle: Best Practices
description: View our recommendations for performance and reliability when using custom database action scripts.
---

## Secure access to your external user store

When you use a custom database connection, you need to provide an interface to your user store so Auth0 can connect to it.

If you do this by making your user store broadly accessible via the internet, you incur significant risks. For example, interfaces for SQL and other databases expose significant functionality, and making them generally available violates the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege).

### Provide access with a protected API

We recommend providing access with an API that has discrete number of protected endpoints to perform only the user management functionality required for the custom database connection, like read user and change password.

Protecting this API with use of an <Tooltip tip="Access Token: Authorization credential, in the form of an opaque string or JWT, used to access an API." cta="View Glossary" href="/docs/glossary?term=access+token">access token</Tooltip> allows you to use the client credentials grant flow from within an action script. You can subsequently cache the token for re-use within the `global` object to improve performance.

If your external user store has an API available, or if you implement one yourself, you can [register the API](/docs/get-started/auth0-overview/set-up-apis) through Auth0, and [create an Action](/docs/manage-users/access-control/sample-use-cases-actions-with-authorization#deny-access-to-anyone-calling-an-api) to restrict access from end users.

By default, Auth0 can give you a token for any API if you authenticate successfully and include the appropriate <Tooltip tip="Audience: Unique identifier of the audience for an issued token. Named aud in a token, its value contains the ID of either an application (Client ID) for an ID Token or an API (API Identifier) for an Access Token." cta="View Glossary" href="/docs/glossary?term=audience">audience</Tooltip>. Restricting access to your user store's API by restricting access token allocation prevents unauthorized usage by only granting access using specific client credentials. This mitigates a number of attack vector scenarios, such as a bad actor intercepting redirects to `/authorize` and adding the audience to the API.

If your external user store does not have an API available and implementing one is not feasible, you can still write your action scripts to [communicate with it directly](#restrict-network-access).

### Restrict network access

We recommend restricting access to your external user store with an IP allowlist including [Auth0's outbound IP addresses](/docs/secure/security-guidance/data-security/allowlist) to allow inbound traffic from Auth0.

Because Auth0's outbound IP addresses are shared between all Auth0 tenants in the given region, we recommend against using such an allowlist as the sole method of securing access to your user store. Instead, use an allowlist as one of several security measures.

## Time out async and external calls

If you call an external service or API within your action script, set the function to time out after a reasonable duration, and return an error if the external service or API cannot be reached.

<AccordionGroup>
<Accordion title="Promise object example">
This example uses the built-in JavaScript `fetch` method, which gets a resource from a network then returns a Promise object, written using [promise chains](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#chaining).

```javascript lines expandable
function login(userNameOrEmail, password, callback) {
const hashedPassword = hash(password);
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
body: {
email: userNameOrEmail,
password: hashedPassword
}
};

fetch(apiEndpoint, options)
.then((response) => {
if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}

return response.json();
})
.then((response) => {
if (response.err) {
return callback(new Error(`Error authenticating user: ${err}`));
}

let profile = {
email: response.profileData.email,
username: response.profileData.username
};

return callback(null, profile);
})
.catch((err) => {
return callback(new Error(`An error occurred: ${err}`));
});
}
Comment on lines +40 to +74

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function login(userNameOrEmail, password, callback) {
const hashedPassword = hash(password);
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
body: {
email: userNameOrEmail,
password: hashedPassword
}
};
fetch(apiEndpoint, options)
.then((response) => {
if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}
return response.json();
})
.then((response) => {
if (response.err) {
return callback(new Error(`Error authenticating user: ${err}`));
}
let profile = {
email: response.profileData.email,
username: response.profileData.username
};
return callback(null, profile);
})
.catch((err) => {
return callback(new Error(`An error occurred: ${err}`));
});
}
async function login(userNameOrEmail, password, callback) {
try {
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: userNameOrEmail,
password: password // Send plaintext over HTTPS; API verifies it
})
};
const response = await fetch(apiEndpoint, options);
if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}
const result = await response.json();
if (result.err) {
return callback(new Error(`Error authenticating user: ${result.err}`));
}
const profile = {
email: result.profileData.email,
username: result.profileData.username
};
return callback(null, profile);
} catch (err) {
return callback(err);
}
}

```
</Accordion>

<Accordion title="Async function example">
This example uses the built-in JavaScript `fetch` method, which gets a resource from a network then returns a Promise object, written using [async functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function).

```js lines expandable
async function login(userNameOrEmail, password, callback) {
const hashedPassword = hash(password);
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
body: {
email: userNameOrEmail,
password: hashedPassword
}
};

const response = await fetch(apiEndpoint, options);

if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}

const result = response.json();

if (result.err) {
return callback(new Error(`Error authenticating user: ${err}`));
}

let profile = {
email: response.profileData.email,
username: response.profileData.username
};

return callback(null, profile);
}
Comment on lines +82 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
async function login(userNameOrEmail, password, callback) {
const hashedPassword = hash(password);
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
body: {
email: userNameOrEmail,
password: hashedPassword
}
};
const response = await fetch(apiEndpoint, options);
if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}
const result = response.json();
if (result.err) {
return callback(new Error(`Error authenticating user: ${err}`));
}
let profile = {
email: response.profileData.email,
username: response.profileData.username
};
return callback(null, profile);
}
⏺ async function login(userNameOrEmail, password, callback) {
try {
const apiEndpoint = 'https://example.com/api/authenticate';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: userNameOrEmail,
password: password
})
};
const response = await fetch(apiEndpoint, options);
if (!response.ok) {
return callback(new Error(`HTTP error! Status: ${response.status}`));
}
const result = await response.json();
if (result.err) {
return callback(new Error(`Error authenticating user: ${result.err}`));
}
const profile = {
email: result.profileData.email,
username: result.profileData.username
};
return callback(null, profile);
} catch (err) {
return callback(err);
}
}

```
</Accordion>
</AccordionGroup>

You can return errors from custom database action scripts by passing them to [the `callback` function](/docs/authenticate/database-connections/custom-db/custom-database-connections-scripts#callback). We recommend using descriptive error messages to help with troubleshooting and debugging.

## Avoid anonymous functions

You can implement action scripts as [anonymous functions](https://developer.mozilla.org/en-US/docs/Glossary/IIFE), but we recommend using function names because anonymous functions can make it difficult to interpret the call stack when debugging [error conditions](./error-handling).

## Retrieve identity provider tokens

If the `user` object returns the `access_token` and `refresh_token` properties, Auth0 handles them differently from other types of user information. Auth0 stores them in the `user` object's `identities` property:

```json lines
{
"email": "you@example.com",
"updated_at": "2019-03-15T15:56:44.577Z",
"user_id": "auth0|some_unique_id",
"nickname": "a_nick_name",
"identities": [
{
"user_id": "some_unique_id",
"access_token": "e1b5.................92ba",
"refresh_token": "a90c.................620b",
"provider": "auth0",
"connection": "custom_db_name",
"isSocial": false
}
],
"created_at": "2019-03-15T15:56:44.577Z",
"last_ip": "192.168.1.1",
"last_login": "2019-03-15T15:56:44.576Z",
"logins_count": 3
}
```

To retrieve either of these properties with the Auth0 <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>, include the `read:user_idp_tokens` scope when [requesting an Access Token](/docs/secure/tokens/access-tokens/management-api-access-tokens/get-management-api-access-tokens-for-production).
Loading
Loading