Sign Up For Closed Loop Only

Direct Authentication Example 3

In this example, a fictitious app called InstantGift uses the direct authentication API calls to sign up a new user with email address ex3@example.com. The user has not registered before. Because the app only uses closed loop cash, Bridge Community Bank will require just one authentication factor (email or SMS), with no password.

Prerequisites

  • The app developer needs to Create an App Object and use the client_id from the app object. In this example, the client_id is 4954253560 and the client_secret is S3krit4TestApp.
  • The app developer needs to get or create a per-device UUID. The device UUID may be a UUID provided by the device’s operating system, or the UUID may be randomly generated and stored.

Step 1

Email/SMS Form

The user enters nothing but their email address (or SMS number) on the first form. In the API, the field is called login.

The app should ask for only an email address or SMS number on the first form (no name or password yet) because users often forget whether they have signed up for a service or not; the direct authentication API is designed to automatically guide existing users who start the sign-up flow into the sign-in flow instead.

Step 2

The user entered ex3@example.com in the login field. The app calls POST /aa/signup-closed.

Full API Call URL
https://api.wingcash.com/aa/signup-closed
API Call Documentation
POST /aa/signup-closed
Headers

Content-Type: application/json

Authorization: Basic ***

Most HTTP request libraries and utilities have a feature for generating a Basic authentication header. See Basic App Authentication.

Request body
{
    "device_uuid": "907fb623-a4a9-4b59-b952-ad783bea7246",
    "login": "ex3@example.com",
    "client_id": "4954253560"
}
cURL example
curl https://api.wingcash.com/aa/signup-closed \
    -u '4954253560:S3krit4TestApp' \
    -H 'Content-Type: application/json' \
    -d '{"device_uuid": "907fb623-a4a9-4b59-b952-ad783bea7246",
        "login": "ex3@example.com",
        "client_id": "4954253560"}'
Response status code
200
Response body
{
    "attempt_path":"/aa/5618911532/",
    "captcha_required":false,
    "code_length":9,
    "factor_id":"fb546e07",
    "revealed_codes": ["073467126 => email:ex3@example.com"],
    "secret":"q9ieEMNt0G3RbgTBjlOWy5C7vhY",
    "trust30":false,
    "unauthenticated": {
        "email:ex3@example.com": {
            "country":null,
            "original":"ex3@example.com"
        }
    }
}

The response is an AuthnResult object that contains Initial Attributes, Code Entry Attributes, and some State Attributes. It does not have any Final Attributes yet.

The AuthnResult includes attempt_path and secret, the Initial Attributes of an AuthnResult. The path and secret will be used for the rest of the calls in this authentication flow. The path and secret should be used for one flow only. The app should have no need to store the attempt_path or secret in a persistent (non-volatile) way.

The AuthnResult also includes factor_id, code_length, unauthenticated, and revealed_codes, the Code Entry Attributes of an AuthnResult. These attributes suggest that the app should ask the user to enter the code they received at the email address specified by the unauthenticated attribute, which is ex3@example.com. Because this example is based on a sandbox instance of OPN, the revealed_codes attribute is present in the AuthnResult, revealing the secret code sent to the user (as a convenience for development and testing.) That attribute is not available in production.

Finally, the AuthnResult includes captcha_required and trust30, some of the State Attributes of an AuthnResult. They indicate that the user has not recently tried to abuse the service, so no CAPTCHA is currently required, and the user has not indicated that they want to trust the device they’re using for 30 days. The rest of the state attributes are not present because the user has not yet finished authenticating.

At the same time, the platform sends an email to ex3@example.com, telling the user the code they should enter. The email contains the text similar to the following (unless the message template has been customized for the site):

Thank you for creating an account at InstantGift!

Enter the following code when prompted by InstantGift:

073467126

This email contains private information, so please do not forward or share it. If you have any questions, email us at support@instantgift.example.com.

Step 3

The app uses its implementation of the Authentication Decision Tree to decide what to do next.

  1. Because the AuthnResult has no completed_mfa attribute, the expression !result.completed_mfa evaluates as true and the app continues into the first conditional block of the tree.
  2. Because the AuthnResult has a non-empty factor_id attribute, the expression !result.factor_id && !result.profile_id evaluates as false and the app skips the corresponding conditional block.
  3. Because the AuthnResult has a non-empty factor_id attribute, the expression result.factor_id evaluates as true and the app executes the corresponding conditional block.
  4. The app sets the next_interaction variable to enter-code and finishes the decision tree.

Code Entry Form

Because the next_interaction is enter-code, the app shows a form that asks the user to enter the code they received in email. The form may show some helpful hints provided by the AuthnResponse:

  • The code is 9 digits in length (from code_length)
  • The code was sent to ex3@example.com (from unauthenticated)

As shown in the decision tree comments, the app should call POST /aa/(string:id)/auth-uid when the user completes the form.

Step 4

The user enters the code and the app submits the code to the POST /aa/(string:id)/auth-uid API call. In production, the app will not know whether the code is correct until the API call responds.

Full API Call URL
https://api.wingcash.com/aa/5618911532/auth-uid
API Call Documentation
POST /aa/(string:id)/auth-uid
Headers
Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY" Content-Type: application/json
Request body
{
    "factor_id": "fb546e07",
    "code": "073467126",
}
cURL example
curl https://api.wingcash.com/aa/5618911532/auth-uid \
    -H 'Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY"' \
    -H 'Content-Type: application/json' \
    -d '{"factor_id": "fb546e07", "code": "073467126"}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex3@example.com":{
            "country":null,
            "original":"ex3@example.com",
            "strong":true,
            "used_password":false
        }
    },
    "captcha_required":false,
    "completed_mfa":true,
    "invite_id":null,
    "profile_id":null,
    "profile_title":null,
    "signup":null,
    "trust30":false
}

The response contains an updated AuthnResult. Because this is a continuation of an authentication flow rather than the first API call, this AuthnResult lacks Initial Attributes; the app is expected to remember the attempt_path and secret for the API calls within this flow.

This AuthnResult also lacks Code Entry Attributes because the API is not expecting the user to enter a code at this moment.

This AuthnResult has State Attributes. Of particular interest:

  • authenticated indicates that the user has proven they can receive messages at email address ex3@example.com.
  • completed_mfa is true, indicating that no more authentication factors are required.
  • profile_id is null, indicating the platform has no wallet connected with the email address ex3@example.com.

Step 5

The app uses its implementation of the Authentication Decision Tree to decide what to do next.

  1. Because the completed_mfa attribute is true, the expression !result.completed_mfa evaluates as false and the app skips the first top-level conditional block of the decision tree.
  2. Because the AuthnResult has a profile_id of null, the expression !result.profile_id evaluates as true and the app proceeds into the corresponding conditional conditional block.
  3. Because the AuthnResult has a signup attribute of null, the expressions result.signup && result.signup.has_password and result.signup && result.signup.name_checked evaluate as false and the app proceeds into the else block for requesting the user’s name.
  4. The app sets the next_interaction variable to set-personal-name and finishes the decision tree.

Name Form

Because the next_interaction is set-personal-name, the app shows a form that asks the user to enter their first and last name. As shown in the decision tree comments, the app should call POST /aa/(string:id)/set-signup-data when the user completes the form.

Step 6

The user enters the name “Jacques Black” and the app submits the information to the POST /aa/(string:id)/set-signup-data API call.

Full API Call URL
https://api.wingcash.com/aa/5618911532/set-signup-data
API Call Documentation
POST /aa/(string:id)/set-signup-data
Headers
Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY" Content-Type: application/json
Request body
{
    "first_name": "Jacques",
    "last_name": "Black"
}
cURL example
curl https://api.wingcash.com/aa/5618911532/set-signup-data \
    -H 'Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY"' \
    -H 'Content-Type: application/json' \
    -d '{"first_name": "Jacques", "last_name": "Black"}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex3@example.com":{
            "country":null,
            "original":"ex3@example.com",
            "strong":false,
            "used_password":false
        },
    },
    "captcha_required":false,
    "completed_mfa":true,
    "invite_id":null,
    "profile_id":null,
    "profile_title":null,
    "signup":{
        "first_name":"Jacques",
        "has_password":false,
        "last_name":"Black",
        "name_checked":true
    },"trust30":false
}

The response contains an updated AuthnResult that now has signup data. The user does not yet have a wallet.

Step 7

The app uses its implementation of the Authentication Decision Tree to decide what to do next.

  1. Because the completed_mfa attribute is true, the expression !result.completed_mfa evaluates as false and the app skips the first top-level conditional block of the decision tree.
  2. Because the AuthnResult has a profile_id of null, the expression !result.profile_id evaluates as true and the app proceeds into the corresponding conditional conditional block.
  3. Because the AuthnResult has a signup attribute with has_password set to false and name_checked set to true, the app proceeds into the next block.
  4. Because this app uses closed loop cash only and does not need users to set a password, this flow chooses the alternative documented in the tree and sets the next_interaction variable to agreement and finishes the decision tree.

Agreement Form

The app shows the agreement form. The agreement form that explains the app’s terms and privacy policy. Once the user accepts the app’s terms and privacy policy, the app should call POST /aa/(string:id)/signup-finish.

Step 8

The user completes the agreement form and the app calls the POST /aa/(string:id)/signup-finish API call.

Full API Call URL
https://api.wingcash.com/aa/5618911532/signup-finish
API Call Documentation
POST /aa/(string:id)/signup-finish
Headers
Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY" Content-Type: application/json
Request body
{
    "agreed": true
}
cURL example
curl https://api.wingcash.com/aa/5618911532/signup-finish \
    -H 'Authorization: opn secret="q9ieEMNt0G3RbgTBjlOWy5C7vhY"' \
    -H 'Content-Type: application/json' \
    -d '{"agreed": true}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex3@example.com":{
            "country":null,
            "original":"ex3@example.com",
            "strong":false,
            "used_password":false
        },
    },
    "captcha_required":false,
    "completed_mfa":true,
    "invite_id":null,
    "profile":{
        "accepted_national_currencies":[],
        "address":"",
        "address_data":null,
        "chain_id":null,
        "first_name":"Jacques",
        "id":"4356518574",
        "image125":null,
        "image24":null,
        "image25":null,
        "image250w":null,
        "image48":null,
        "image50":null,
        "image73":null,
        "is_individual":true,
        "last_name":"Black",
        "latitude":"",
        "longitude":"",
        "phone":"",
        "preferred_currency":"",
        "support_paycode":false,
        "title":"Jacques Black",
        "unsupported_national_currencies":[],
        "url":"https://wingcash.com/p/4356518574/",
        "username":null,
        "wingcash_uid":"wingcash:4356518574"
    },
    "profile_id":"4356518574",
    "profile_title":"Jacques Black",
    "signup":{
        "first_name":"Jacques",
        "has_password":false,
        "last_name":"Black",
        "name_checked":true
    },
    "token":{
        "access_token":"t4954253560-7284763818-kaW6ppOwOHcKZ-Q-25GPneef2Mk",
        "expires_in":899,
        "hard_expires_in":316223999,
        "scope":"accept_offer change_settings edit_account list_friends manage_account manage_sent mobile_device send_cash send_to_account view view_full_history view_history view_wallet",
        "token_type":"bearer"
    },
    "trust30":false
}

The AuthnResult has State Attributes along with the Final Attributes, token and profile.

Step 9

The app uses its implementation of the Authentication Decision Tree to decide what to do next.

  1. Because the completed_mfa attribute is true, the expression !result.completed_mfa evaluates as false and the app skips the first top-level conditional block of the decision tree.
  2. Because the AuthnResult has a profile_id, the expression !result.profile_id evaluates as false and the app skips the second top-level conditional block as well.
  3. The app sets the next_interaction variable to authenticated and finishes the decision tree.

Wallet Created

The user is now authenticated and has a wallet the app can use, so the app proceeds to its main view. The access token (to be used for most other API calls) is in the access_token sub-attribute of the AuthnResult’s token attribute.