Sign Up For Open Loop Access

Direct Authentication Example 1

In this example, a fictitious app called InstantAutoPay uses the direct authentication API calls to sign up a new user with email address ex1@example.com. The user has not registered before. Bridge Community Bank requires reliable authentication and security for accessing open loop cash, so the user will need to set up a password and 2 second factors. (Only one of the second factors will be required to sign in again.)

This example is a highly detailed description of a short flow that is optimized for a clean and clear user experience. There are only a few forms and each form is minimal.

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.
  • 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 ex1@example.com in the login field. The app calls POST /aa/signup.

Full API Call URL
https://api.wingcash.com/aa/signup
API Call Documentation
POST /aa/signup
Headers
Content-Type: application/json
Request body
{
    "device_uuid": "907fb623-a4a9-4b59-b952-ad783bea7246",
    "login": "ex1@example.com",
    "client_id": "4954253560"
}
cURL example
curl https://api.wingcash.com/aa/signup \
    -H 'Content-Type: application/json' \
    -d '{"device_uuid": "907fb623-a4a9-4b59-b952-ad783bea7246",
        "login": "ex1@example.com",
        "client_id": "4954253560"}'
Response status code
200
Response body
{
    "attempt_path":"/aa/8025915877/",
    "captcha_required":false,
    "code_length":6,
    "factor_id":"7cce202b",
    "revealed_codes": ["623652 => email:ex1@example.com"],
    "secret":"GQJ6lBJONnrByb2BQl0Xe0jOMRE",
    "trust30":false,
    "unauthenticated": {
        "email:ex1@example.com": {
            "country":null,
            "original":"ex1@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 ex1@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 ex1@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 InstantAutoPay!

Enter the following code when prompted by InstantAutoPay:

623652

This email contains private information, so please do not forward or share it. If you have any questions, email us at support@instantautopay.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 6 digits in length (from code_length)
  • The code was sent to ex1@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/8025915877/auth-uid
API Call Documentation
POST /aa/(string:id)/auth-uid
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "factor_id": "7cce202b",
    "code": "623652",
}
cURL example
curl https://api.wingcash.com/aa/8025915877/auth-uid \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"factor_id": "7cce202b", "code": "623652"}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex1@example.com":{
            "country":null,
            "original":"ex1@example.com",
            "strong":false,
            "used_password":false
        }
    },
    "captcha_required":false,
    "completed_mfa":false,
    "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 ex1@example.com.
  • completed_mfa is false, indicating that the API needs more authentication information.
  • profile_id is null, indicating the platform has no wallet connected with the email address ex1@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 false, 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 no factor_id attribute and the profile_id attribute is null, the expression !result.factor_id && !result.profile_id evaluates as true and the app executes the corresponding conditional conditional block.
  3. The app sets the next_interaction variable to add-factor and finishes the decision tree.

SMS Number Form

Because the next_interaction is add-factor, the app shows a form that asks the user to enter their phone number to serve as another authentication factor. Because the format of phone numbers varies by country, it may be necessary to include a field on the form that lets users select the country for the phone number. Alternatively, the app developer may choose to provide a hardcoded list of countries where the app may be used, but that list should be short because phone numbers are often valid in multiple countries. A third alternative is to interpret the phone number within the app and submit the full E.164 internationalized phone number in the API call (with the standard + prefix).

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

Step 6

The user enters the phone number 202-555-1111 and the app submits the number to the POST /aa/(string:id)/add-factor API call.

Full API Call URL
https://api.wingcash.com/aa/8025915877/add-factor
API Call Documentation
POST /aa/(string:id)/add-factor
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "login": "202-555-1111",
    "countries": ["US", "GB"],
}
cURL example
curl https://api.wingcash.com/aa/8025915877/add-factor \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"login": "202-555-1111", "countries": ["US", "GB"]}'
Response status code
200
Response body
{
    "captcha_required":false,
    "code_length":6,
    "factor_id":"7963b341",
    "revealed_codes":["559258 => phone:+12025551111"],
    "trust30":false,
    "unauthenticated":{
        "phone:+12025551111":{
            "country":"US",
            "original":"(202) 555-1111"
        }
    }
}

This AuthnResult response contains Code Entry Attributes and a couple of State Attributes. The platform automatically interprets the phone number input and converts it to E.164 international format. It also converts the number to the format preferred in the associated country.

At the same time, the platform sends a text message to +12025551111, telling the user the code they should enter. The message contains text similar to the following (unless the message template has been customized for the site):

[InstantAutoPay] Use code 559258 to continue creating an account. (Do not share this private code.)

Step 7

This step is very similar to 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 a text message. The form may show some helpful hints provided by the AuthnResponse:

  • The code is 6 digits in length (from code_length)
  • The code was sent to (202) 555-1111 (from the original sub-attribute in 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 8

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/8025915877/auth-uid
API Call Documentation
POST /aa/(string:id)/auth-uid
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "factor_id": "7963b341",
    "code": "559258",
}
cURL example
curl https://api.wingcash.com/aa/8025915877/auth-uid \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"factor_id": "7963b341", "code": "559258"}'
Response status code
200
Response body
{
    "authenticated": {
        "email:ex1@example.com": {
            "country":null,
            "original":"ex1@example.com",
            "strong":false,
            "used_password":false
        },
        "phone:+12025551111": {
            "country":"US",
            "original":"(202) 555-1111",
            "strong":false,
            "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. This AuthnResult lacks Initial Attributes and Code Entry Attributes, but it has State Attributes. Of particular interest:

  • authenticated indicates that the user has proven access to both an email address and a phone number.
  • completed_mfa is true, indicating multi-factor authentication is complete.
  • profile_id is still null, indicating the platform has not yet created a wallet for the user.

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 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 10

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/8025915877/set-signup-data
API Call Documentation
POST /aa/(string:id)/set-signup-data
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "first_name": "Jacques",
    "last_name": "Black"
}
cURL example
curl https://api.wingcash.com/aa/8025915877/set-signup-data \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"first_name": "Jacques", "last_name": "Black"}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex1@example.com":{
            "country":null,
            "original":"ex1@example.com",
            "strong":false,
            "used_password":false
        },
        "phone:+12025551111":{
            "country":"US",
            "original":"(202) 555-1111",
            "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 11

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 block for asking the user to set a password.
  4. The app sets the next_interaction variable to set-password and finishes the decision tree.

Set Password Form

Because the next_interaction is set-password, the app shows a form that asks the user to set their password. The form should either ask for the password twice (verifying the passwords are identical before executing the next API call) or show the password to the user for confirmation. 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 12

The user enters the password “jellydonut” and the app calls the POST /aa/(string:id)/set-signup-data API call.

Full API Call URL
https://api.wingcash.com/aa/8025915877/set-signup-data
API Call Documentation
POST /aa/(string:id)/set-signup-data
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "password": "jellydonut"
}
cURL example
curl https://api.wingcash.com/aa/8025915877/set-signup-data \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"password": "jellydonut"}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex1@example.com":{
            "country":null,
            "original":"ex1@example.com",
            "strong":false,
            "used_password":false
        },
        "phone:+12025551111":{
            "country":"US",
            "original":"(202) 555-1111",
            "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":true,
        "last_name":"Black",
        "name_checked":true
    },
    "trust30":false
}

The response contains an updated AuthnResult that is very similar to step 10, except that it now has signup data, including a password. The user does not yet have a wallet.

Step 13

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 true, the app proceeds into the block for asking the user to accept the app’s terms.
  4. The app sets the next_interaction variable to agreement and finishes the decision tree.

Agreement Form

Because the next_interaction is agreement, the app shows an 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 14

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/8025915877/signup-finish
API Call Documentation
POST /aa/(string:id)/signup-finish
Headers
Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE" Content-Type: application/json
Request body
{
    "agreed": true
}
cURL example
curl https://api.wingcash.com/aa/8025915877/signup-finish \
    -H 'Authorization: opn secret="GQJ6lBJONnrByb2BQl0Xe0jOMRE"' \
    -H 'Content-Type: application/json' \
    -d '{"agreed": true}'
Response status code
200
Response body
{
    "authenticated":{
        "email:ex1@example.com":{
            "country":null,
            "original":"ex1@example.com",
            "strong":false,
            "used_password":false
        },
        "phone:+12025551111":{
            "country":"US",
            "original":"(202) 555-1111",
            "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":true,
        "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 15

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.