Custom Function Authentication Problems and Solutions

Like the user linked below, I need the ability to do user administration client-side.

Administrate Realm users on client

While Custom Function Authentication opens a door for that, I found it difficult in practice to get everything working in a streamlined way. I’m outlining my process here in the hopes 1) that it might be helpful to others dealing with these issues and 2) that someone can potentially offer more appropriate solutions for some of my problems because many of my solutions feel like hacks.

Overall, it feels like everything surrounding Custom Function Authentication is a little half-baked, though hopefully I’m just missing something.

Problem: Realm provides no client-side tools for user management.

Solution: Create your own user collection, then use a Custom Function Authentication. But…

Problem: Managing custom users (writing rules, dealing with permissions, etc.) is difficult because the relationship between Realm Users and custom users isn’t very robust.

Solution: Enable Custom User Data and point it to your user collection so that all of your user data is in user.customData. But…

Problem: The User ID Field that Realm looks at in the Custom User Data collection doesn’t exist initially. Further, Authentication Triggers do not seem to support Custom Function Authentication, making it difficult to create a relationship between a Realm User and your Custom User Data collection.

Solution: Create a function that uses context.user.identities[0].id to find the appropriate entry in your Custom User Data collection and populate it with the Realm User ID. Then call this function client-side after every login, followed by user.refreshCustomData(). Now user.customData will work for server-side rule authoring and for client-side tasks. This is the most frustrating hack by far.

Problem: There seems to be no way to fail a Custom Function Authentication gracefully. Either you return a proper value for a new/existing user, or it just fails, so you can’t return any useful info about why the attempt was invalid.

Solution: Call the authentication function from a webhook first to find any potential issues, then use Custom Function Authentication only if there are no problems.

Unsolved/Minor Issues

  • user.data is empty for Custom Function Authentication users. You can still look in context.user.identities[0], but it could be annoying if you have multiple identities.

  • The Realm Users page doesn’t show any useful info for Custom Function Authentication users. At a minimum it would ideally show the internal id used to create it (context.user.identities[0].id).

  • Deleting a user from the custom collection leaves a Realm user behind. Presumably I could fix this by building a clean-up tool with the Admin API.

That’s where I am so far and things are more or less working, though I am very open to feedback or alternative solutions.

4 Likes

Hi @Scott_Garner,

Thank you for sharing your insights. I think this kind of posts can become a useful blog post for our users.

Since I was working for a long time with MongoDB Realm (from its initial Stitch days) I can understand how there is no single perfect auth provider which on one hand will offer an easy robust authentication API and on the other hand cover all use cases such as full administration capabilities.

I find what you have posted for the Custom Function Authentication very interesting and I need to read those points in depth to understand them.

However, I wanted to offer some thoughts and progress I made with Emaill/Password administration and the Admin API from Realm functions/webhooks without exposing the Admin API keys/tokens.

The following code can facilitate an access to the API by using Secrets from the Application, this code can be placed in an “admin” webhook with service rules allowing only admins to run it:

// Get Atlas Parameters and application id
  const AtlasPrivateKey = context.values.get("AtlasPrivateKey");
  const AtlasPublicKey = context.values.get("AtlasPublicKey");
  const AtlasGroupId = context.values.get("AtlasGroupId");
  const appId = '<APP-ID>';
  
  
  // Authenticate to Realm API
  const respone_cloud_auth = await context.http.post({
    url : "https://realm.mongodb.com/api/admin/v3.0/auth/providers/mongodb-cloud/login",
    headers : { "Content-Type" : ["application/json"],
                 "Accept" : ["application/json"]},
    body : {"username": AtlasPublicKey, "apiKey": AtlasPrivateKey},
    encodeBodyAsJSON: true
                                                  
  });
    
   const cloud_auth_body = JSON.parse(respone_cloud_auth.body.text());
   
   // Get the internal appId
  const respone_realm_apps = await context.http.get({
    url : `https://realm.mongodb.com/api/admin/v3.0/groups/${AtlasGroupId}/apps`,
    headers : { "Content-Type" : ["application/json"],
                 "Accept" : ["application/json"],
                 "Authorization" : [`Bearer ${cloud_auth_body.access_token}`]
    }
                                                  
  });
     
   const realm_apps = JSON.parse(respone_realm_apps.body.text());
   
   
   var internalAppId = "";
   
   realm_apps.map(function(app){ 
     if (app.client_app_id == appId)
     {
       internalAppId = app._id;
     }
     });
   
   
   // Get all realm users 
    const respone_realm_users = await context.http.post({
    url : `https://realm.mongodb.com/api/admin/v3.0/groups/${AtlasGroupId}/apps/${internalAppId}/users`,
    headers : { "Content-Type" : ["application/json"],
                 "Accept" : ["application/json"],
                 "Authorization" : [`Bearer ${cloud_auth_body.access_token}`],
                 body : {
                           "email": "string",
                          "password": "string"
                          },
    encodeBodyAsJSON: true
    }
                                                  
  });
    
    
   const realm_users = JSON.parse(respone_realm_users.body.text());

Additionally, the Email/Password can have a confirmation function. A user can register and be pending until the admin which can be notified via an email triggered by a confirmation function, approves him (can be done in an email having an HTML link/button to run the confirmation flow).

Thanks
Pavel

4 Likes

A post was split to a new topic: How to handle errors on Custom Function Authentication?