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:
Build your own login UI using the Quasr Authentication API.
Configure clients in Quasr to use your login UI instead of the default Hosted Login UI.
Test and validate the complete flow with your own login UI (using a client test option).
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 flowother OAuth 2.0 parameters for which there's not need to understand them — please capture these so they can be used in step 6
This guide only details the happy flow — please be mindful that much related to building a good login UI has to do with dealing with unhappy flows. Hence significant more effort will be required in creating a customer-ready 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
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"
}
]
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
client_id*
UUID
ID of the client
Request Body
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
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
}
To avoid invalid input errors please look at the regex
specified on the factor in step 1. Our Hosted Login UI uses this field to asses the input in the front-end before passing it on towards the API.
The body in above calls is an array object as follows:
[
{
"id": "<factor_id|control_id>",
// other parameters
}
]
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!)
}
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.
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
client_id*
UUID
ID of the client
Headers
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
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"
}
]
Notice that the call format of these APIs are identical to that of step 1 - the only difference is that it's now called with the session_token
in the Authorization
header.
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
Authorization*
String
Request Body
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
Authorization*
String
session token
Request Body
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 body in above calls is an array object as follows (currently only 1 entry is supported):
[
{
"id": "<factor_id>",
// other parameters
}
]
Notice that the call format of this API is identical to that of step 2 - the only difference is that it's now called with the session_token
in the Authorization
header.
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.
5. Obtain / Grant Consent
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).
Obtain and/or grant consent for a specific client.
POST
https://{tenant_id}.api.quasr.io/controls
Obtain and/or grant consent for a specific client.
Query Parameters
client_id*
UUID
ID of the client
Headers
Authorization*
JWT
session token
Request Body
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!)
}
It is especially important as signup to replace the existing session_token
with the one returned in this call. As the new session will be a login session with a longer lifetime, else the user will be asked to log back in shortly after registration.
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
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
Each consent token can only be used exactly once.
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.

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)

Currently the pop-up dialog is not using the configured login URL but defaulting to the hosted login UI for your tenant, hence please make sure to edit the URL before testing.
Last updated