Custom Function Authentication
On this page
Overview
The custom function authentication provider allows you to define a custom authentication flow using a serverless function. You can use this provider to implement your own user authentication logic or integrate an external authentication system.
When to Use Custom Function Authentication
Custom function authentication is the most flexible form of authentication. However, it also requires you to manually define and configure the authentication flow yourself.
Before you define a custom function provider, consider if you can one use of the built-in authentication providers instead.
Consider using the custom function provider in your app if:
You want to use an external authentication service that does not have a built-in provider. If the service uses JSON web tokens, consider creating a Custom JWT provider instead.
You want to customize the authentication process beyond what's available in a built-in provider. For example, you could use a service to send customized confirmation emails instead of the default email/password provider emails.
Important
Atlas App Services does not perform any data validation or authentication checks for the custom function provider.
Make sure that you validate incoming data and that your authentication system performs appropriate authentication checks, such as requiring a password, two-factor authentication, or a single sign-on token.
The following diagram shows the custom function logon process:
The Authentication Function
The authentication function is a JavaScript function that holds your custom user authentication code. It runs whenever a user logs in through the custom function provider.
The function maps data provided at login, like a username and password or an access token, to a string that uniquely identifies the user in your external authentication system. For example, you could use the provided data to log in to an external service over HTTP or using a package from npm.
exports = async function (payload) { // 1. Parse the `payload` object, which holds data from the // FunctionCredential sent by the SDK. const { username, password } = payload; // 2. Create a new user or log in an existing user in the external // authentication service. // You can use a client library from npm const auth = require("fake-auth-service"); const user = await auth.login({ username, password }); // Or you can communicate directly over HTTP const userFromHttp = await context.http.post({ url: "https://example.com/auth/login", headers: { Authorization: ["Basic bmlja0BleGFtcGxlLmNvbTpQYTU1dzByZA=="], }, body: JSON.stringify({ username, password }), }); // 3. Return a unique identifier for the user. Typically this is the // user's ID in the external authentication system or the _id of a // stored MongoDB document that describes them. // // !!! This is NOT the user's internal account ID for your app !!! return user.id; };
Receive a Custom Credential Payload
The payload
object passed to the function contains data that was
included with the custom function provider credential in the client app.
The function accepts any value provided from your client app, so the
actual field names and values depend on your implementation.
For examples of how to create a custom credential payload
object
using the Realm SDKs, refer to the documentation for each SDK:
Return an Authenticated User ID
If authentication is successful, the function should return a unique string identifier for the user. For example, you could return the user ID value used by your external authentication system. This is the user's external ID, which the provider uses to map from your custom system to your app's internal user accounts.
Important
The user's external ID is not the same as the user's internal
account ID, which is exposed as the id
field of a user object.
You access the user's internal ID with %%user.id
in expressions,
context.user.id
in functions, and the User.id
property in the
SDKs.
If an existing user is already associated with the external ID, the provider logs that user in.
If the provider has no record of a given external ID, it creates a new user account, adds a custom function provider identity, and then logs in the new user.
The identity object for the custom function provider is stored in the user object and resembles the following:
{ "id": "<Internal User Account ID>", "identities": [ { "providerType": "custom-function", "id": "<External User ID>", } ] }
Example
The authentication function should return a unique external ID as a string:
return "5f650356a8631da45dd4784c"
You can also return an object that contains the external ID as its
id
value:
return { "id": "5f650356a8631da45dd4784c" }
If you want to define a display name for the user, define it in the
name
field of the returned object:
return { "id": "5f650356a8631da45dd4784c", "name": "James Bond" }
Throw an Error for Failed Authentication
If the user provided invalid credentials or the function otherwise fails
to authenticate the user, throw an error with a descriptive message.
This returns a 401 - Unauthorized
error with the message attached to
the SDK.
const auth = require("some-external-auth-system"); try { const user = await auth.login(payload); return user.id; } catch (err) { throw new Error(`Authentication failed with reason: ${err.message}`); }
Set Up the Custom Function Provider
You can configure custom function authentication using any supported deployment method.
Define the Authentication Function
The provider uses a normal function to handle authentication. The function should return a unique external ID string to identify the user.
To define a new authentication function:
Click the Function dropdown and select New Function.
Enter a name for the function. This name must be unique among all functions in your application.
Define the source code in the function editor.
Click Save
Deploy the Updated Application
To make custom function authentication available to client applications, deploy your application.
Click Deploy in the left navigation menu
Find the draft in the deployment history table and then click Review & Deploy Changes.
Review the diff of changes and then click Deploy.
Define the Authentication Function
The provider uses a normal function to handle authentication. The function should return a unique external ID string to identify the user. user.
Save the authentication function code
in the functions
directory.
touch functions/handleCustomFunctionAuthentication.js
Add a Provider Configuration File
To enable and configure the Custom Function authentication provider,
define a a configuration object for it in
/auth/providers.json
.
Custom function provider configurations have the following form:
{ "custom-function": { "name": "custom-function", "type": "custom-function", "config": { "authFunctionName": "<Authentication Function Name>" }, "disabled": false } }
Configure Custom User Data
You can associate custom data in a MongoDB Atlas collection with user accounts in your app. This can be helpful if you often need to access a user's data but is not required to use the custom function provider.
A user's custom data document may contain any data. For apps that use the custom function provider, we recommend storing both the user's internal user account ID and their external ID.
For example, you might use the following format:
{ "_id": "<Generated ObjectId>", "user_id": "<Internal User ID>", "external_id": "<External User ID>" }
You can use the following approach to create custom user documents for custom function provider users:
Configure custom user data for a collection in your linked cluster. The User ID field stores the user's internal account ID.
/auth/custom_user_data.json{ "mongo_service_name": "mongodb-atlas", "database_name": "myApp", "collection_name": "users", "user_id_field": "user_id", "enabled": true } Configure the custom function authentication provider and return a unique external user ID from the authentication function. App Services stores this ID in the
id
field of the user'scustom-function
identity.exports = async function handleCustomFunctionAuth(payload) { const auth = require("some-external-auth-system"); const user = await auth.login(payload); return user.id; }; Set up an authentication trigger that listens for
CREATE
events from thecustom-function
provider. In the trigger function, add a new document to the custom user data collection that includes both the user's internal ID and external ID.exports = async function onNewCustomFunctionUser({ user }) { // This is the user's internal account ID that was generated by your app const internalId = user.id; // This is the external ID returned from the authentication function const customFunctionIdentity = user.identities.find((id) => { return id.provider_type === "custom-function"; }); const externalId = customFunctionIdentity.id; // Create a custom user data document for the user const mdb = context.services.get("mongodb-atlas"); const users = mdb.db("myApp").collection("users"); return await users.insertOne({ // Include both the internal ID and external ID user_id: internalId, external_id: externalId, // Add any other data you want to include created_at: new Date(), }); };
Log In from a Realm SDK
To log in from a client application, use a Custom Function credential that contains your login payload data.
For examples, refer to the documentation for a specific SDK: