Docs Menu
Docs Home
/ /
Atlas App Services
/ /

Custom Function Authentication

On this page

  • Overview
  • When to Use Custom Function Authentication
  • The Authentication Function
  • Receive a Custom Credential Payload
  • Return an Authenticated User ID
  • Throw an Error for Failed Authentication
  • Set Up the Custom Function Provider
  • Configure Custom User Data
  • Log In from a Realm SDK

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.

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:

Custom Function authentication flow diagram

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;
};

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:

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"
}

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}`);
}

You can configure custom function authentication using any supported deployment method.

1

To enable the provider:

  1. Click App Users in the left navigation menu.

  2. Select the Providers tab.

  3. Click Custom Function Authentication.

  4. On the provider configuration screen, set the Provider Enabled toggle to On.

The list of authentication providers in the App Services UI with the Custom Function provider highlighted.
click to enlarge
2

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:

  1. Click the Function dropdown and select New Function.

  2. Enter a name for the function. This name must be unique among all functions in your application.

  3. Define the source code in the function editor.

  4. Click Save

The function selection dropdown in the App Services UI.
click to enlarge
3

To make custom function authentication available to client applications, deploy your application.

  1. Click Deploy in the left navigation menu

  2. Find the draft in the deployment history table and then click Review & Deploy Changes.

  3. Review the diff of changes and then click Deploy.

1

To pull a local copy of the latest version of your app, run the following:

appservices pull --remote="<Your App ID>"

Tip

You can also download a copy of your application's configuration files from the Deploy > Export App screen in the App Services UI.

2

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
3

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:

/auth/providers.json
{
"custom-function": {
"name": "custom-function",
"type": "custom-function",
"config": {
"authFunctionName": "<Authentication Function Name>"
},
"disabled": false
}
}
4

Once you have created the authentication function and configured the provider, you can push the updated configurations to your remote app. App Services CLI immediately deploys the new schema on push.

appservices push --remote="<Your App ID>"

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:

  1. 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
    }
  2. 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's custom-function identity.

    exports = async function handleCustomFunctionAuth(payload) {
    const auth = require("some-external-auth-system");
    const user = await auth.login(payload);
    return user.id;
    };
  3. Set up an authentication trigger that listens for CREATE events from the custom-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(),
    });
    };

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:

Back

Apple ID