Creating users with Password using API - User CRUD

I need to be able to create a batch of accounts in the helper file, including setting the password. How can I create the appropriate hash in FileMaker?

Occasionally there is a need to create a users account programmatically.

TLDR:

  • You cant generate PW hashes
  • The easiest solution is to create the account in the helper and send a reset email

If you need to be able to send a reset link that will not time bomb, then you instead need to jump though some auth hoops since you are potentially weakening security.

This diagram goes over the basic concept

https://whimsical.com/Cg9wBnJ4X6As6dK7iu985g

We are currently working on an app where accounts will only be created programatically and NEVER by self registration.

The old WebDirect App creates accounts and presets the password which is emailed to the new user. (I realise this is not good security practice, but I have inherited this solution)

Time limited reset urls will not really work for our scenario.

I need a user friendly process for creating users programatically. Do you have any suggestions?

Does this sound like a possible process:

  • Create the accounts using the User API
  • Send out the following link: https://xxxxx.fmbetterforms.com/#/login/forgot
  • User enters their email address and clicks the “Send Link” button
  • Password reset process then proceeds as normal.
1 Like

Yes, thats a fairly easy to do process.

The next step from there would be to skip a step

  • Create the accounts using the User API
  • Send out a link to the forget page,
  • in forgotPW onFormLoad Hook check for a query for an email, if present:
  • Send the email a reset link via authForgot Action with email in the model
  • Show on page “Check your email to get started on the new system…”

This is the same but you save them entering an email ( they may however want to change emails )
and you click the button for them.

1 Like

I like this approach as it simplifies the process I’ve implemented. I have a similar setup where accounts are created in the app, then registration links are pushed to the user. If the user tries to register an email that is not in the app, then I use the user API to delete that account from the helper file. On the flip side, by having a user “setup” their own account, it does reinforce their user name and password.

Charles, We have been trying to implement the process you describe but have been unable to pass back the resetToken to the browser after the authForgot action is called. we have successfully intercepted the email, but anything we put in the model appears to be stripped out before it gets back to the browser.

Any suggestions would be most appreciated.

As a security measure BF will always strip out PW’s from hooks and should.
But, you can ‘save’ the PW in the app model before the hook runs.

Then after the hook, you can set model.password = app.password and do things like reset PW, log in etc.

The issue is that we can’t get the resetToken back to the browser

BF appears to be stripping out everything not just passwords

Just realised I never updated this thread.

We did eventually get this working to the point where we can create a BF user by calling the script “API - User CRUD { user }” in the helper file fro the FileMaker solution and sending a link to the new user which will enable them to go directly to the page where they enter their new password.

I am confident we can easily modify this to enable logged in users to change their password without the email loop.

A recent post by @MJ-APJ also has a flowchart of how to solve this.

@APJ Would it be possible for you to post the details needed to accomplish this workflow? I have a client that is requesting this very workflow.

This post is a response to a thread on Slack asking how to steamline the process for new users setting their password and avoiding the need to send a link which when clicked goes to a page where the email address must be entered before the form is submitted to send a second email with a second link to a form where the actual password is set.

I have tried to share all that is needed, but I may have missed something, so do ask if you think that is the case…

The example FileMaker file contains the required scripts and can be downloaded from https://apjuk.com/files/apj/BF_Login_Hookset.fmp12

For the avoidance of doubt, we found we needed a “login” scoped hookset to get this to work.

The Auth - Reset page in BetterForms is as follows

Page Builder Panel

[{
        "schema": {
            "groups": [{
                "fields": [{
                    "html": "<div class=\"h-16 bg-black flex justify-between sm:relative sm:rounded-t-lg\">\n    <div class=\"sm:absolute ml-2 py-2\"><img :src=app.env.logoURL class=\" h-12 m-auto\">\n    </div>\n    <h1 class=\"text-white text-left sm:text-center text-2xl sm:text-3xl font-bold m-auto px-3\">{{model.pageTitle}}</h1>\n</div>",
                    "styleClasses": "max-w-screen-sm mx-auto sm:mt-4",
                    "type": "html"
                }],
                "styleClasses": ""
            }, {
                "fields": [{
                    "autocomplete": "new-password",
                    "featured": true,
                    "hint": "Must be at least 10 characters",
                    "inputType": "password",
                    "label": "Password",
                    "min": 10,
                    "model": "password",
                    "placeholder": "Enter password",
                    "required": true,
                    "styleClasses": "",
                    "type": "input",
                    "validator": "string"
                }, {
                    "autocomplete": "new-password",
                    "errorMsg": "Passwords must match",
                    "featured": true,
                    "hint": "Must match",
                    "inputType": "password",
                    "label": "Confirm password",
                    "min": 10,
                    "model": "password2",
                    "placeholder": "Enter password again",
                    "required": true,
                    "styleClasses": "",
                    "type": "input",
                    "validator": "calc",
                    "validator_calc": "model.password == model.password2"
                }, {
                    "html": "<p class=\"text-center\">{{model.authMessage}}</p>",
                    "styleClasses": "",
                    "type": "html",
                    "visible_calc": "model.authMessage && model.showMsg"
                }, {
                    "actions": [{
                        "action": "validate",
                        "options": {}
                    }, {
                        "action": "runUtilityHook",
                        "function": "model.isLoading = true;\nmodel.authMessage = '';\nmodel.showMsg = true;",
                        "options": {
                            "type": "getResetToken"
                        }
                    }, {
                        "action": "authReset"
                    }, {
                        "action": "function",
                        "function": "model.isLoading = false",
                        "options": {}
                    }],
                    "buttonClasses": "btn btn-primary btn-block",
                    "disabled_calc": "model.isLoading",
                    "icon_calc": "model.isLoading ? 'fa fa-circle-o-notch fa-spin' : ''",
                    "model": "",
                    "styleClasses": "",
                    "submit": true,
                    "text": "Set Password",
                    "type": "button"
                }, {
                    "actions": [{
                        "action": "path",
                        "options": {
                            "path": "/login"
                        }
                    }],
                    "buttonClasses": "btn btn-link btn-block",
                    "styleClasses": "-mt-2",
                    "text": "Login",
                    "type": "button"
                }],
                "styleClasses": "-mt-4 px-5 pt-3 bg-blue-200 max-w-screen-sm mx-auto text-black sm:rounded-b-lg"
            }]
        },
        "styleClasses": ""
    }]

Data Model Panel

{
    "formID": "FR_5FFDE113-F9F0-2E49-AB32-2965AE09EFF7",
    "isLoading": false,
    "pageTitle": "Reset Password",
    "showMsg": false
}

Misc Panel

{
    "authLevel": 0,
    "description": "Reset password landing page\rNeeds a nav slug of `login/reset`\rAuthentication must be OFF",
    "formName": "LTA - Auth - Reset",
    "formType": "formblank",
    "hideHeader": true,
    "hookSetName": "login",
    "id": "Deprecated",
    "isForm": true,
    "namedActions": {
        "onFormLoad": [{
            "action": "setFocus",
            "function": "if(BF.getQueryParam('customToken')) model.pageTitle = 'Set Password';\ndocument.title = 'LTA - Application - ' + model.pageTitle;\n//if(model.email) window.EventBus.$emit('processNamedAction',{\"action\": \"namedAction\", \"name\": \"runAuthForgot\", \"options\": {}});",
            "options": {
                "elementId": "password"
            }
        }]
    },
    "requestHook": true,
    "sendFullSchema": false,
    "showPanelHeader": false,
    "styleClasses": "required-optional",
    "text": "",
    "validateOnBack": false
}

I’m working on integrating this into my system and when I go to the Link that is sent out via the email it comes up with the new password reset page, but when I enter in the password and email is sent to me with a link to reset my password. The password I entered does not get saved.

Is it possible there are some changes made to the helper file that have not been outlined in the post above? Once again thank you for providing this example it has been extremely helpful.

Sorry, I forgot to include the changes to the onAuthNotifier script changes are shown in bold when type = sendResetPwd or type = resetPwd

BF - onAuthNotifier - globalhookset in file Solution

Set Variable [ $$BF_Log ; Value: List( $$BF_Log; “@” & Get(ScriptName) & " - Start" ) ]
Set Variable [ $schema ; Value: JSONGetElement ( $$BF_Payload ; “data” ) ]
Set Variable [ $type ; Value: JSONGetElement ( $schema ; “type” ) ]
Set Variable [ $email ; Value: JSONGetElement ( $schema ; “user.email” ) ]
Set Variable [ $resetToken ; Value: JSONGetElement ( $schema ; “user.resetToken” ) ]
Set Variable [ $verifyToken ; Value: JSONGetElement ( $schema ; “user.verifyToken” ) ]
Set Variable [ $url ; Value: “https://” & JSONGetElement ( $schema ; “state.host” ) &"/#" ]

#Handle each notifier type (sendResetPwd, … )
If [ $type = “sendResetPwd” ]
Set Variable [ $id_betterforms ; Value: JSONGetElement ( $$BF_User ; “id” ) ]
Set Variable [ $customToken ; Value: GetColumn ( staff::customToken ; staff::id_bf ; $id_betterforms ) ]
If [ IsEmpty ( $customToken ) ]
Set Variable [ $url ; Value: $url & “/login/reset?token=” & $resetToken ]
Set Variable [ $subject ; Value: “User Password Reset” ]
Set Variable [ $message ; Value: List( “You have requested a password reset.”; “”; “Please follow this link to reset your password: " & $url; “If you did not make this request, please disregard this email” ) ]
Else
Perform Script [ Specified: From list ; “onAuthNotifier - reset - save resetToken” ; Parameter: ]
End If
#
Else If [ $type = “resendVerifySignup” //Send Email asking user to verify email address ]
Set Variable [ $url ; Value: $url & “/login/verify?token=” & $verifyToken ]
Set Variable [ $subject ; Value: " Verify Email” ]
Set Variable [ $message ; Value: List( “Please verify your email by clicking the link below.”; “”; "Link: " & $url; “If you did not make this request, please disregard this email” ) ]
#
Else If [ $type = “verifySignup” ]
#This branch runs when the user has successfuly verified their email
// Set Variable [ $$BF_actions ; Value: BF_SetAction_path ( “/login” ; “” ) ]
// Set Variable [ $$BF_Actions ; Value: BF_SetAction_showAlert( “SAVED” ; “success” ; “You can now login” ; “” ) ]
#
Else If [ $type = “resetPwd” ]
#This branch runs when the user successfully updates thier password.
If you wanted to notify the reset was successful, this is where it would go
Set Variable [ $$BF_actions ; Value: BF_SetAction_path ( “/login” ; “” ) ]
Set Variable [ $$BF_Actions ; Value: BF_SetAction_showAlert ( “Success” ; “success” ; “Password Changed” ; “{}” ) ]
Perform Script [ Specified: From list ; “onAuthNotifier - reset - clear Tokens” ; Parameter: ]
Else
Set Variable [ $$BF_Actions ; Value: BF_SetAction_showModal( “Unhandled Notifier” ; “warning” ; “check the onAuthNotifier script” ; “dark” ; “” ) ]
End If

Love this workflow, but I’m having trouble with the initial link.

I create a user record in the Helper File with the “API - User CRUD { user }” script. But how is the original custom token generated for the user to be sent to the password reset page?

When we create a new record in our application staff table, the customToken field Autoenters a uuid which is used to create the link sent out by FileMaker (after the CRUD script is used to create the BF user)

https://domain.co.uk/#/login/reset?customToken=” & staff::customToken

Great! So I think I’m almost there. Everything seems to be working up until I click the “Set Password” button. Even though the reset token has been created and is being pulled, I get an error that says “Token is not in the correct format.” and no password is stored.

The token needs to be in the query of the URL OR in the model under the token key, can check there to make sure.

When I try to run the workflow, it creates three records in the helper inbox.

The first two are created when the url with the custom token is opened

  1. onFormRequest - Inbound: customToken from url Outbound: no resetToken
  2. onAuthNotifier - Inbound: resetToken in model Outbound: the model is pretty much empty

The third is when you click “Set Password”
3. onUtility - Inbound: no token Outbound: token is in the model

Where is it supposed to be?