API-Documentation: OAuth 2.0
All standard OAuth APIs use sake_case for both request and response parameters to comply with the OAuth 2.0 specifications. The introspect API is specific to Liquid which alone uses camelCase.
For web applications (React, Vue, Next.js, Node.js), you can use the official liquid-js-sdk package to call all Liquid APIs with zero boilerplate and full TypeScript support.
Official OAuth 2.0 Specifications & Core Concepts
If you are new to OAuth 2.0, Liquid adheres strictly to official IETF standards and RFC specifications. Below are key references and guides to help you understand the core concepts:
- Authorization Code Grant (RFC 6749 § 4.1) — Standard authorization flow for web applications and SPAs (OAuth.net Authorization Code Guide).
- Client Credentials Grant (RFC 6749 § 4.4) — Machine-to-machine server authentication (OAuth.net Client Credentials Guide).
- PKCE - Proof Key for Code Exchange (RFC 7636) — Essential security extension for public clients (SPAs and Mobile apps) (OAuth.net PKCE Guide).
- Refresh Token Grant (RFC 6749 § 6) — Token renewal without forcing user re-login (OAuth.net Refresh Token Guide).
- OAuth 2.0 for Browser-Based Apps (IETF BCP) — Best Current Practice guidelines for Single Page Applications (SPAs).
Client Types: Confidential vs Public Clients
Liquid supports two types of OAuth 2.0 clients:
- Confidential Clients (
isPublic: false): Web applications running on backend servers (Node.js, Go, Python, etc.) capable of keeping aclient_secrethidden from users.- Authentication: Uses Client Credentials Grant or Authorization Code Grant with
client_secretduring token exchange (POST /oauth/token).
- Authentication: Uses Client Credentials Grant or Authorization Code Grant with
- Public Clients (
isPublic: true): Single Page Applications (React, Vue, Angular) or Native/Mobile apps running on user devices where secrets cannot be stored securely.- Authentication: Do not use a
client_secret. Must use PKCE (RFC 7636) during authorization and token redemption.
- Authentication: Do not use a
Setting Up PKCE for Public Clients (SPAs / Mobile Apps)
PKCE protects public clients against authorization code interception attacks.
PKCE Flow Summary
- Generate Ephemeral Key Pair: Generate a cryptographically random
code_verifierstring (43-128 chars) and its SHA-256 hash (code_challenge). - Authorize Request: Pass
code_challengeandcode_challenge_method=S256to/oauth/authorize. - Token Request: Redeem the authorization code at
/oauth/tokenby passingcode_verifier(without aclient_secret).
Example: Generating PKCE in JavaScript (Web Crypto API)
// 1. Generate code_verifier
function generateCodeVerifier(): string {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, Array.from(array)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
// 2. Derive code_challenge (SHA-256)
async function generateCodeChallenge(verifier: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await window.crypto.subtle.digest("SHA-256", data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
// Usage before redirecting to /oauth/authorize:
const verifier = generateCodeVerifier();
sessionStorage.setItem("pkce_code_verifier", verifier);
const challenge = await generateCodeChallenge(verifier);
// Redirect to Liquid authorize endpoint:
const authUrl = `/oauth/authorize?response_type=code` +
`&client_id=your_spa_client_id` +
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
`&code_challenge=${encodeURIComponent(challenge)}` +
`&code_challenge_method=S256`;
Client Authentication
Access Token
Exchange your client ID and secret for an access token.
Before You Start
You can adjust the validity of the access token by changing the option oauth.access-token-lifetime to the intended number of seconds. By default, the validity is 1 hour.
URL
POST /oauth/token
Request Body (Form Data)
| Parameter | Type | Description | Required / Optional |
|---|---|---|---|
| grant_type | string | Set to "client_credentials" | Required |
| client_id | string | Your client ID. | Required |
| client_secret | string | Your client secret. | Required |
| scope | string | Comma separated list of scopes. | Required |
Request Sample (JSON)
{
"grant_type": "client_credentials",
"client_id": "application_client",
"client_secret": "super-secure-client-secret",
"scope": "client:profile:read"
}
Response Parameters
| Parameter | Type | Description |
|---|---|---|
| access_token | string | The access token. |
| token_type | string | The token type. Usually "Bearer". |
| expires_in | number | Token expiry in seconds. |
Response Sample
{
"access_token": "cfb1983f82d187ac26598dca27aa3078236e645ccb18d77bdcba100d62a99231",
"token_type": "Bearer",
"expires_in": 3600
}
Introspect [Non-Standard OAuth]
Verify delegated tokens.
Before You Start
Any other microservice in your application apart from Liquid that need to provide an authentication to the frontend will use this API. For instance, let's say a chat service in your system needs to verify the user is authenticated to access your application. You will call this API with the Liquid access token and it would return the user information after verifying the authenticity of the token. The API also returns the scopes allowed for the token. Using all this information, you can choose to allow or disallow access to your chat API.
For microservices running on node, use the Liquid Node Authenticator. This will already use introspect API under the hood and makes your integration much easier.
For more information, see Understanding Access Control and Integrating with Other Microservices
URL
POST /oauth/token/introspect
Request Body (Form Data)
| Parameter | Type | Description | Required / Optional |
|---|---|---|---|
| token | string | Token of the user to be authenticated. (Acquired using authorization code grant) | Required |
Request Sample (JSON)
{
"token": "7eb2b49801f4ff3bbce93531dcf559c75504013cc0d7c2d03c49a529dedf4d72"
}
Response Data Parameters
| Parameter | Type | Description |
|---|---|---|
| tokenInfo | object | Information about the token. |
Response Sample
{
"ok": 1,
"data": {
"tokenInfo": {
"accessToken": "7eb2b49801f4ff3bbce93531dcf559c75504013cc0d7c2d03c49a529dedf4d72",
"accessTokenExpiresAt": "2023-09-26T18:54:14.821Z",
"scope": "delegated:all,admin:all",
"user": {
"_id": "507f1f77bcf86cd799439011",
"username": "john_doe",
"firstName": "John",
"lastName": "Doe",
"preferredLanguage": "en",
"role": "user",
"verified": true,
"followingCount": 1,
"followerCount": 0,
"isPrivate": false,
"email": "johndoe@example.com",
"emailVerified": true,
"isSubscribed": false,
"credits": 0,
"isActive": true,
"isBanned": false,
"isRestricted": false,
"deleted": false,
"createdAt": "2023-06-21T13:20:00.112Z",
"updatedAt": "2023-09-04T14:52:15.442Z",
"customLink": "https://custom.link",
"profilePicturePath": null
}
}
}
}
Delegated Authentication
Get Authorization Code
Get an authorization code after login.
Before You Start
- If you're using Liquid's built in login and signup UI, you will never need to access this API. Simply redirect to
/loginwith the required params and you'll be redirected back to your application with the authorization code and state if everything goes well. - You can adjust the validity of the authorization code by changing the option
oauth.authorization-code-lifetimeto the intended number of seconds. By default, the validity is 5 minutes. - PKCE: For public clients (
isPublic: true) or whenoauth.authorization.require-pkceoption istrue,code_challengeandcode_challenge_methodare required.
URL
POST /oauth/token
Request Body (Form Data)
| Parameter | Type | Description | Required / Optional |
|---|---|---|---|
| response_type | string | Set to code | Required |
| client_id | string | Your client ID. | Required |
| redirect_uri | string | An authorized redirect URI. | Required |
| state | string | Application state. | Required |
| scope | string | Comma separated list of scopes. | Required |
| code_challenge | string | PKCE code challenge (SHA-256 hash of code_verifier). | Required for Public Clients / PKCE |
| code_challenge_method | string | PKCE code challenge method (S256 or plain). | Required for Public Clients / PKCE |
Request Sample (JSON)
{
"response_type": "code",
"client_id": "application_client",
"code": "the_code",
"redirect_uri": "https://my-redirect.uri/path",
"state": "xxx-yyy-zzz",
"scope": "delegated:all",
"code_challenge": "E9Melhvh10-5gq_7BBNW6g3Jy3G2k_85n6_g1j45",
"code_challenge_method": "S256"
}
Response
A redirect to your callback URL with code and state as URL parameters.
Access Token (From Authorization Code)
Exchange authorization code for an access token.
Before You Start
- You can adjust the validity of the access token by changing the option
oauth.access-token-lifetimeto the intended number of seconds. By default, the validity is 1 hour. - You can adjust the validity of the refresh token by changing the option
oauth.refresh-token-lifetimeto the intended number of seconds. By default, the validity is 15 days. - Client Authentication: Confidential clients must present
client_secret. Public clients using PKCE must presentcode_verifier.
URL
POST /oauth/token
Request Body (Form Data)
| Parameter | Type | Description | Required / Optional |
|---|---|---|---|
| grant_type | string | Set to authorization_code | Required |
| code | string | Your authorization code | Required |
| client_id | string | Your client ID. | Required |
| client_secret | string | Your client secret. | Required for Confidential Clients |
| code_verifier | string | PKCE code verifier string generated during authorize. | Required for Public Clients / PKCE |
| redirect_uri | string | An authorized redirect URI. | Required |
| scope | string | Comma separated list of scopes. | Required |
Request Sample (JSON)
{
"grant_type": "authorization_code",
"client_id": "application_client",
"client_secret": "super-secure-client-secret",
"code": "the_code",
"redirect_uri": "https://my-redirect.uri/path",
"scope": "delegated:all"
}
Response Parameters
| Parameter | Type | Description |
|---|---|---|
| access_token | string | The access token. |
| token_type | string | The token type. Usually "Bearer". |
| expires_in | number | Token expiry in seconds. |
| refresh_token | string | The refresh token. |
Response Sample
{
"access_token": "7eb2b49801f4ff3bbce93531dcf559c75504013cc0d7c2d03c49a529dedf4d72",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "95068fcd6ed814ebd14981903c280dce0ff86bc794c1121cc1b10be4fd588c7a"
}
Access Token (From Refresh Token)
Exchange a refresh token to get an access token.
URL
POST /oauth/token
Request Body (Form Data)
| Parameter | Type | Description | Required / Optional |
|---|---|---|---|
| grant_type | string | Set to refresh_token. | Required |
| refresh_token | string | The refresh token. | Required |
| client_id | string | Your client ID. | Required |
Request Sample (JSON)
{
"grant_type": "refresh_token",
"client_id": "application_client",
"refresh_token": "7eb2b49801f4ff3bbce93531dcf559c75504013cc0d7c2d03c49a529dedf4d72"
}
Response Parameters
| Parameter | Type | Description |
|---|---|---|
| access_token | string | The access token. |
| token_type | string | The token type. Usually "Bearer". |
| expires_in | number | Token expiry in seconds. |
| refresh_token | string | The refresh token. |
Response Sample
{
"access_token": "7eb2b49801f4ff3bbce93531dcf559c75504013cc0d7c2d03c49a529dedf4d72",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "95068fcd6ed814ebd14981903c280dce0ff86bc794c1121cc1b10be4fd588c7a"
}