OAuth2 Customer Authentication
This guide explains how to authenticate users using OAuth2 in our system. It covers the authorization flow, the use of PKCE (code_challenge), and how to obtain your client credentials.
Getting Your Client ID
Section titled “Getting Your Client ID”To use OAuth2, you need a Client ID. You can find or create your API client credentials in the admin panel:
- Go to Administration in the backend.
- Navigate to Users & Roles.
- Click on the Manage dropdown.
- Select API Clients.
- Here you can view, create, or manage your API clients and obtain the Client ID.
Authentication Schemes
Section titled “Authentication Schemes”1. Authorization Code Flow (default)
Section titled “1. Authorization Code Flow (default)”Our API uses the OAuth2 Authorization Code flow with PKCE. This is a secure way to authenticate users, especially for mobile and single-page applications.
This flow means that you will arrive at a WhiteBeard-hosted login page.
Step 1: Generate a Code Verifier and Code Challenge
Section titled “Step 1: Generate a Code Verifier and Code Challenge”If you are using a public key, the Code Challenge is required. Follow these steps. If you are using a private client, you can skip this step.
- Code Verifier: A random string (43-128 characters).
- Code Challenge: A Base64-URL-encoded SHA256 hash of the code verifier.
Example (in PHP):
$codeVerifier = bin2hex(random_bytes(64));$codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');Example (in JavaScript):
async function generateCodeVerifierAndChallenge() { // Generate a random code verifier (hex string of 64 random bytes → 128 characters) const randomBytes = new Uint8Array(64); crypto.getRandomValues(randomBytes); const codeVerifier = Array.from(randomBytes) .map(byte => byte.toString(16).padStart(2, '0')) .join('');
// Hash the code verifier using SHA-256 const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); const hashBuffer = await crypto.subtle.digest('SHA-256', data);
// Base64-url encode the hash const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(hashBuffer))) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '');
return [codeVerifier, codeChallenge];}Step 2: Redirect User to the Authorization Endpoint
Section titled “Step 2: Redirect User to the Authorization Endpoint”Send the user to the /oauth2/authorize endpoint with the following parameters:
response_type=codeclient_id=YOUR_CLIENT_IDredirect_uri=YOUR_CALLBACK_URLcode_challenge=CODE_CHALLENGEcode_challenge_method=S256scope=...(optional)state=...(recommended for CSRF protection)
Example URL:
https://api.yourdomain.com/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK_URL&code_challenge=CODE_CHALLENGE&code_challenge_method=S256&state=xyzStep 3: User Grants Access
Section titled “Step 3: User Grants Access”The user logs in and approves access. They are redirected to your redirect_uri with a code parameter.
Step 4: Exchange Code for Access Token
Section titled “Step 4: Exchange Code for Access Token”Make a POST request to /oauth2/access_token with:
grant_type=authorization_codeclient_id=YOUR_CLIENT_IDredirect_uri=YOUR_CALLBACK_URLcode=CODE_FROM_STEP_3code_verifier=YOUR_CODE_VERIFIER
Example (cURL):
curl -X POST https://yourdomain.com/oauth2/access_token \ -d 'grant_type=authorization_code' \ -d 'client_id=YOUR_CLIENT_ID' \ -d 'redirect_uri=YOUR_CALLBACK_URL' \ -d 'code=CODE_FROM_STEP_3' \ -d 'code_verifier=YOUR_CODE_VERIFIER'The authorization server will respond with a JSON object containing the following properties:
token_typewith the value Bearerexpires_inwith an integer representing the TTL of the access tokenaccess_tokena new JWT signed with the authorization server’s private keyrefresh_tokenan encrypted payload that can be used to refresh the access token when it expires.
2. Password login
Section titled “2. Password login”You can use the less-safe method of username/password-based authentication. In this case, you can set the following values on the /oauth2/access_token request:
grant_type=passwordclient_id=YOUR_CLIENT_IDclient_secret=CLIENT_SECRETusername=CUSTOMER_USERNAMEpassword=CUSTOMER_PASSWORDscope=basic
The authorization server will respond with a JSON object containing the following properties:
token_typewith the value Bearerexpires_inwith an integer representing the TTL of the access tokenaccess_tokena new JWT signed with the authorization server’s private keyrefresh_tokenan encrypted payload that can be used to refresh the access token when it expires
3. One-time PIN (OTP) login
Section titled “3. One-time PIN (OTP) login”You can login users without a password, using a one-time pin (OTP). Follow these steps:
- Do a
POST /customer/login/request. API reference.- If you are using a confidential/private OAuth2.0 client, you must send the
client_passwordfield. - If you are using a public client, follow the same Auth Code flow instructions for generating a
code_challengewithcode_challenge_method=S256.
- If you are using a confidential/private OAuth2.0 client, you must send the
- If your request body is valid, you will always receive HTTP 200 with
{"completed": true}regardless of whether the customer account exists or not, and whether you are rate-limited or not. See the notes below about rate-limiting. - If the e-mail exists in the system, automations with the trigger
Customer login requestare triggered.- Configure an Automation for this trigger.
- Set it to send the customer an e-mail.
- Use the macro
{{code}}in a text layer to show the code.
- Once the user types in the code on your frontend, perform
POST /oauth2/access_token. API reference.- The same rules apply for
client_secretandcode_verifier. grant_typemust be set tootp.usernamemust be set type the email of the user.- You will receive an
access_tokenandrefresh_tokenif successful.
- The same rules apply for
Useful notes
Section titled “Useful notes”- Each e-mail address is rate-limited at 1 login request per minute.
- OTP login codes are valid for 2 minutes. We aim to increase this to 10 minutes based on customer feedback.
- A maximum of 5 attempts are allowed for a code. After that, it is destroyed.
- When a failed attempt occurs, the code is set to expire 1 minute from the current time. Consecutive failed attempts will keep moving the expiry forward by 1 minute to a maximum of 5 attempts or a maximum of 10 minutes of continuous attempts.
- We plan to add IP-based rate-limiting which will require you to forward the Customer’s IP address in the case of a confidential client.
4. External authentication (Facebook Login, Google Login, Apple Login)
Section titled “4. External authentication (Facebook Login, Google Login, Apple Login)”We also support logging in via third-party authenticators. The currently supported providers are:
- Facebook Login
- Google Login
- Apple Login
Your custom setup can also be supported as an authentication backend. Contact our support for more information.
The authentication flow is as follows:
- Implement the third-party’s login flow on your application
- Redirect the user to login to that provider
- On return, read the access token retrieved (or auth code)
- Send a POST to
/oauth2/access_tokenwithgrant_type=passwordsimilar to the password-based authentication. In the username field, you would input the name of the provider. You will need a token or auth code to perform the login. This varies based on the provider. Refer to the examples below.
For Facebook login, use the following values:
username=facebookpassword=JSON_STRING
The JSON string must a JSON-encoded object containing:
token: Facebook access tokensource: The login or user registration source. Used typically when a new registration is triggered.
Example:
{"token":"abcxyz", "source":"website"}For Apple login, use the following values:
username=applepassword=JSON_STRING
The JSON string must a JSON-encoded object containing:
token: Apple JWT access tokenname: The full name of the user (read upon initial login or sometimes not provided)source: The login or user registration source. Used typically when a new registration is triggered.
Example:
{"token":"abcxyz", "name": "my full name", "source":"website"}For Google login, use the following values:
username=applepassword=JSON_STRING
The JSON string must a JSON-encoded object containing:
token: Google access tokensource: The login or user registration source. Used typically when a new registration is triggered.
Or if you wish for the system to exchange an auth_code on your behalf, you may provide an auth code:
auth_code: Auth code from Google OAuth2 processsource: The login or user registration source. Used typically when a new registration is triggered.
Example:
{"token":"abcxyz", "name": "my full name", "source":"website"}Using the Access Token
Section titled “Using the Access Token”Include the access token in the Authorization header for API requests:
Authorization: Bearer ACCESS_TOKENUsing the Refresh Token
Section titled “Using the Refresh Token”To get a new access token from a refresh token, send a POST request with following body parameters to /oauth2/access_token:
grant_typewith the valuerefresh_tokenrefresh_tokenwith the refresh tokenclient_idwith the the client’s IDclient_secretwith the client’s secret (API Key)scopewith a space-delimited list of requested scope permissions. This is optional; if not sent the original scopes will be used, otherwise you can request a reduced set of scopes.
The authorization server will respond with a JSON object containing the following properties:
token_typewith the value Bearerexpires_inwith an integer representing the TTL of the access tokenaccess_tokena new JWT signed with the authorization server’s private keyrefresh_tokenan encrypted payload that can be used to refresh the access token when it expires
Example (cURL):
curl -X POST https://api.yourdomain.com/oauth2/access_token \ -d 'grant_type=refresh_token' \ -d 'client_id=YOUR_CLIENT_ID' \ -d 'client_secret=YOUR_CLIENT_SECRET' \ -d 'scope=basic' \ -d 'refresh_token=YOUR_REFRESH_TOKEN'