Custom Login UI

With Quasr you can build and use your own login UI while still benefiting from using the standard integration mechanism of OpenID Connect under the hood. High-level the process is as follows:

  1. Build your own login UI using the Quasr Authentication API.

  2. Configure clients in Quasr to use your login UI instead of the default Hosted Login UI.

  3. Test and validate the complete flow with your own login UI (using a client test option).

  4. Connect your app using OAuth 2.0 / OpenID Connect 1.0 identical to Hosted Login UI.

Step 1 - Build Your Own Login UI

First of all please get familiar with the Hosted Login UI as it will give you a good understanding of all of the steps involved and how a login / signup experience could look like. Your custom login UI will received following query parameters:

  • client_id the ID of the client initiating the flow (i.e. the application from where the user is coming or trying to access)

  • scope the scopes the client is requesting (this could be API scopes to obtain access to user details, the Quasr API or for your own API) — the scopes are in 1 string and space-separated.

  • client_label the label of the client initiating the flow

  • other OAuth 2.0 parameters for which there's not need to understand them — please capture these so they can be used in step 6

Login / signup flow with your own custom login UI.

1. Get Available Controls / Factors (optional)

This step is optional as you could choose to hard-code this info in your login UI. The first call will give you information on what controls are required / optional, whether they require consent or permission, and what score you'll need to achieve to obtain (or grant them). The other call gives you which factors are available for login or signup that the user can choose from.

Get public controls for a specific client.

GET https://{tenant_id}.api.quasr.io/controls

Fetch control information for a specific client.

Query Parameters

Name
Type
Description

client_id*

UUID

ID of the client

[
    {
        "consent_required": false,
        "score": 2,
        "subtype": "scope",
        "id": "7665b76c-bf94-4151-8830-748176c4a11e",
        "label": "OpenID Connect 1.0",
        "value": "openid",
        "required": true,
        "permission_required": false,
        "status": "ENABLED"
    },
    {
        "score": 0,
        "subtype": "legal",
        "consent_required": true,
        "id": "d17e58ec-a876-438c-a4e1-a34489b05a00",
        "label": "Data Processing Agreement",
        "value": "https://www.quasr.io/legal/dpa",
        "required": true,
        "permission_required": false,
        "status": "ENABLED"
    },
    {
        "score": 0,
        "subtype": "legal",
        "consent_required": true,
        "id": "535bb044-2c59-46dc-8057-f8b6efea8468",
        "label": "Terms of Service",
        "value": "https://www.quasr.io/legal/tos/202205",
        "required": true,
        "permission_required": false,
        "status": "ENABLED"
    },
    {
        "consent_required": false,
        "score": 5,
        "subtype": "scope",
        "id": "e11b481f-5a22-4e4a-bf92-9898b0fd4416",
        "label": "Account Access",
        "value": "https://api.quasr.io/scopes/account",
        "required": true,
        "permission_required": false,
        "status": "ENABLED"
    }
]

Get public factors for a specific tenant.

GET https://{tenant_id}.api.quasr.io/factors

Fetch the available factors for login / signup.

[
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:facebook",
        "unique": true,
        "id": "b77be83d-4bc2-4cc7-b7d1-fb28e559d978",
        "label": "Facebook",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:apple",
        "unique": true,
        "id": "5ecf49d5-b057-46c5-ae72-8bec6abd84dd",
        "label": "Apple",
        "status": "ENABLED"
    },
    {
        "score": 3,
        "public": true,
        "subtype": "totp",
        "unique": false,
        "id": "4c2c46be-8376-48cf-8702-f281aaf3f1c1",
        "label": "Authenticator App",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:linkedin",
        "unique": true,
        "id": "f380982a-2481-4cc4-8848-758cfd7bccdd",
        "label": "LinkedIn",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:google",
        "unique": true,
        "id": "e510cb04-8b6a-4f28-8297-bfcca9d0ae3c",
        "label": "Google",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:github",
        "unique": true,
        "id": "d48ac274-6abd-44b2-8fe5-e8455d3d3bb1",
        "label": "GitHub",
        "status": "ENABLED"
    },
    {
        "score": 2,
        "regex": "^.{15,100}$",
        "public": true,
        "subtype": "secret:password",
        "unique": false,
        "id": "5169c94a-a244-4aff-b8f3-0bf46f6c5526",
        "label": "Password",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "public": true,
        "subtype": "oauth2:slack",
        "unique": true,
        "id": "9df5b804-7bf0-4fdc-b122-7a5e34378c40",
        "label": "Slack",
        "status": "ENABLED"
    },
    {
        "score": 0,
        "regex": "^.{1,100}$",
        "public": true,
        "subtype": "secret:id",
        "unique": true,
        "id": "e3d99adf-1d99-47fd-8db5-20b36318a60f",
        "label": "Username",
        "status": "ENABLED"
    },
    {
        "score": 3,
        "regex": "^(([^<>()[ \\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$",
        "public": true,
        "subtype": "otp",
        "unique": true,
        "id": "fd26d514-7c72-4ba3-a60f-dccc475d971e",
        "label": "Email One-Time Password",
        "status": "ENABLED"
    }
]

These APIs are highly cached (up to 24 hours) and don't count towards your metrics.

2. Initial Login / Signup

This step is required and the core of the Authentication API. This call will allow an unknown user to login or signup with your tenant.

Public signup for new users.

POST https://{tenant_id}.api.quasr.io/controls

Initiate signup with one or more controls.

Query Parameters

Name
Type
Description

client_id*

UUID

ID of the client

Request Body

Name
Type
Description

id*

UUID

ID of the control

consent

Boolean

consent (for controls that require consent to be provided)

label

String

label to attach to consent

expires_in

String

option to time-box consent

{
    "result": "PENDING",
    "controls": [] // details on the controls
    "account_id": "47655123-1461-4ea3-8374-98b6be3c709d",
    "session_token": "<session_token>",
    "session_score": 0,
    "session_exp": 1690831304
}

Public login for registered accounts.

POST https://{tenant_id}.api.quasr.io/factors

Initiate login with a chosen factor.

Request Body

Name
Type
Description

id*

UUID

ID of the factor

input

String

user input for the factor (depends on chosen factor type)

{
    "result": "SUCCESS",
    "feedback": {
        "cause": "",
        "enrollment_id": "c89035bf-ba8b-46b8-99d0-51398929b37b"
    },
    "session_token": "<session_token>",
    "account_id": "f5f6df52-d68b-4568-be82-057ec44b6a74",
    "session_score": 0,
    "session_exp": 1690916367
}

Input is not always required depending on the chosen factor type:

  • oauth2:x no input needed

  • secret:id user input required for login / recommended for signup (in case missing it will generate an identifier for you during signup, though the generated identifier may be hard to memorize for humans so it's not recommended)

  • otp user input required

Let's zoom in a bit on the returned response.

{
    "result": "PENDING|SUCCESS|FAILED", // inspect this field to assess completion
    // below only when successful (or pending at signup)
    "account_id": "<account_id>", // account ID
    "session_token": "<session_token>", // please store this token in the browser
    "session_score": <session_score>, // attained score => relevant for controls
    "session_exp": "<session_exp>" // expiration in epoch in seconds (not ms!)
}

We recommend storing the session_token in browser LocalStorage.

In case the result is PENDING you have to perform the following:

  • If this occurs at login you'll not receive a session_token and you'll need to call this API again but this time as instructed for the specific factor.

  • If this occurs at signup you'll receive a session_token and you can continue this guide.

If you want to support an invite flow, the invite_token returned upon account creation is basically a session token resembling a signup with no factors (and hence zero score). Hence you can just accept the token from the user and continue with step 3.

3. Get Available Controls / Factors (optional)

This step depends on whether you need to continue building up your score.

Get personalized controls for a specific client.

GET https://{tenant_id}.api.quasr.io/controls

Fetch control information for a specific client.

Query Parameters

Name
Type
Description

client_id*

UUID

ID of the client

Headers

Name
Type
Description

Authorization*

JWT

session token

Get personalized factors for a specific tenant.

GET https://{tenant_id}.api.quasr.io/factors

Fetch the available factors for login / signup.

Headers

Name
Type
Description

Authorization*

JWT

session token

[
    {
        "score": 5,
        "subtype": "oauth2:google",
        "id": "5fb3f5b4-0cc7-4dbf-8a5f-bbc3e1cb68ca",
        "label": "Google",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "subtype": "oauth2:slack",
        "id": "d41c2c8b-4cd8-4cc5-82d5-c9db09f11c31",
        "label": "Slack",
        "status": "ENABLED"
    },
    {
        "score": 2,
        "subtype": "otp",
        "id": "e5cb0c95-5c46-4339-9249-b437a3e0b17b",
        "label": "Business",
        "status": "ENABLED"
    },
    {
        "score": 3,
        "regex": "[0-9]{6}",
        "subtype": "totp",
        "id": "3dc81aa6-be68-4a1f-a0b9-a63934ef5d31",
        "label": "iPhone",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "subtype": "oauth2:facebook",
        "id": "884b3d66-cbfc-440b-bcba-d330e7892577",
        "label": "Facebook",
        "status": "ENABLED"
    },
    {
        "score": 3,
        "subtype": "otp",
        "id": "fa8e042e-3581-4f52-b44f-bb8380dd8e9c",
        "label": "Personal",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "subtype": "oauth2:linkedin",
        "id": "fb3555dc-666e-40db-8512-42d6c4379d65",
        "label": "LinkedIn",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "subtype": "oauth2:github",
        "id": "e3c1b7ed-bb1a-470d-96cb-026f1a6892c4",
        "label": "Github",
        "status": "ENABLED"
    },
    {
        "score": 5,
        "subtype": "oauth2:apple",
        "id": "3c6925f3-3ded-40b5-a562-f5ed39c30e5c",
        "label": "Apple",
        "status": "ENABLED"
    }
]

4. Step-Up Login / Signup (optional)

This step is required in case you need to increase the user's score (for example to obtain or grant certain controls). This call will allow an identified / registered user to continue login or signup with your tenant.

Continue login for a registered account.

POST https://{tenant_id}.api.quasr.io/factors

Continue login with a chosen enrollment.

Headers

Name
Type
Description

Authorization*

String

Request Body

Name
Type
Description

id*

String

ID of the enrollment

input

String

user input for the factor (depends on chosen factor type)

Continue signup for a new user.

POST https://{tenant_id}.api.quasr.io/factors

Continue signup with a chosen factor.

Headers

Name
Type
Description

Authorization*

String

session token

Request Body

Name
Type
Description

id*

String

ID of the factor

input

String

user input for the factor (depends on chosen factor type)

label

String

label to attach to enrollment if succesfull

The output is identical to that of step 2 — make sure to replace your current session_token with the new one returned in this call.

This step is required and part of the core of the Authentication API. This call will allow an identified / registered user to obtain and/or grant consent consent for the client application. This step checks the following:

  • The user has accepted all required legal controls (e.g. Terms & Conditions).

  • The user has achieved the required score to grant scopes to the client application.

  • The user has permission to grant specific scopes to the client application (think admin access).

  • The user has provided consent to grant scopes to the client application (only for external one).

POST https://{tenant_id}.api.quasr.io/controls

Obtain and/or grant consent for a specific client.

Query Parameters

Name
Type
Description

client_id*

UUID

ID of the client

Headers

Name
Type
Description

Authorization*

JWT

session token

Request Body

Name
Type
Description

id*

UUID

ID of the control

consent

Boolean

consent (for controls that require consent to be provided)

label

String

label to attach to consent

expires_in

String

option to time-box consent

{
    "result": "FAILED",
    "controls": [
        {
            "expires": false,
            "result": "SUCCESS",
            "reason": "INTERNAL_ACCOUNT",
            "score": 0,
            "subtype": "legal",
            "id": "535bb044-2c59-46dc-8057-f8b6efea8468",
            "label": "Terms of Service (TOS) v2022.05",
            "value": "https://www.quasr.io/legal/tos/202205",
            "required": true
        },
        {
            "expires": false,
            "result": "SUCCESS",
            "reason": "INTERNAL_ACCOUNT",
            "score": 0,
            "subtype": "legal",
            "id": "d17e58ec-a876-438c-a4e1-a34489b05a00",
            "label": "Data Processing Agreement (DPA)",
            "value": "https://www.quasr.io/legal/dpa",
            "required": true
        },
        {
            "reason": "INSUFFICIENT_SCORE",
            "result": "FAILED",
            "score": 5,
            "subtype": "scope",
            "id": "e11b481f-5a22-4e4a-bf92-9898b0fd4416",
            "label": "Account Access",
            "value": "https://api.quasr.io/scopes/account",
            "required": false
        },
        {
            "reason": "INSUFFICIENT_SCORE",
            "result": "FAILED",
            "score": 2,
            "subtype": "scope",
            "id": "7665b76c-bf94-4151-8830-748176c4a11e",
            "label": "OpenID Connect 1.0",
            "value": "openid",
            "required": true
        }
    ]
}

Let's zoom in a bit on the returned response.

{
    "result": "PENDING|SUCCESS|FAILED", // inspect this field to assess completion
    "controls": [ ... ], // detailed feedback on requested/required controls
    // only if successfull
    "consent_token": "<consent_token>", // you need this for the next step
    "consent_exp": "<consent_exp>", // expiration in epoch in seconds (not ms!)
    "consent_scope": "<consent_scope>", // granted scopes
    "session_token": "<session_token>", // please overwrite session in the browser
    "session_exp": "<session_exp>" // expiration in epoch in seconds (not ms!)
}

6. Finish

This step is required in order to finish the login/signup flow correctly. Make sure you have obtained a consent_token in step 5. This call will result in redirect you'll need to follow (HTTP status 302).

Request access to a specific client.

GET https://{tenant_id}.api.quasr.io/oauth2/authorize

Finish the login/signup flow with a consent token.

Query Parameters

Name
Type
Description

client_id*

String

ID of the client

response_type*

String

pass on as provided

state

String

pass on if provided

nonce

String

pass on if provided

redirect_uri

String

pass on if provided

code_challenge

String

pass on if provided

code_challenge_method

String

pass on if provided

consent*

String

consent token

scope

String

pass on if provided

Step 2 - Configure Login URI

As your login UI is now ready to handle incoming requests you must configure Quasr to start using it. This is currently only possible to be configured on each client (application) individually.

Go to Accounts, find the client, click edit, go to OAuth2 Settings and set your Login URL.

Login URL configuration for a client under OAuth 2.0 settings under Accounts in the Quasr Tenant Admin UI.

Step 3 - Test Login Flow

First make sure that the authorization_code grant type is enabled for you client. In the account list in the Quasr Tenant Admin UI you'll now see the option to test your client. Triggering the flow as such will include two more query parameters to your login UI:

  • prompt=login this is an official OAuth 2.0 parameter, indicating that your login UI should ask the user to login regardless of whether they already have a session (it's not required to support this parameter)

  • mode=test this is a Quasr parameter and is used by the Hosted Login UI to bypass the cache, recall that certain API calls are highly cached, allowing you to more quickly see tenant changes (it's not required to support this parameter)

Option to initiate a login test for a specific client.

Last updated