Skip to content

Auth Guard Module

The Auth Guard module allows you to implement additional authentication and authorization checks during the user login flow. Executed after standard authentication (password, MFA, device verification) but before granting access, this module is ideal for enforcing compliance policies (e.g., legal agreements, security check), adding custom multi-factor authorization, or validating mTLS certificates.

manifest.json
{
"modules": {
"auth-guard": [
{
"key": "your-module-key",
"name": "IP Whitelist Check",
"description": "Verifies user IP address",
"url": "/api/auth/verify",
"options": {
"type": "direct",
"applyToAdmins": false
}
}
]
}
}
key

Type: string

Required: yes

Description: Module identifier within the Crowdin app.

name

Type: string

Required: yes

Description: Human-readable name displayed to users during verification.

description

Type: string

Required: no

Description: Additional description shown to users. Available only for the frame verification type.

url

Type: string

Required: yes

Description: Endpoint URL for verification. Relative to baseUrl.

options

Type: object

Required: no

Description: Module configuration options.

options.type

Type: string

Allowed values: direct, redirect, frame

Default: direct

Description: The verification interaction type.

options.applyToAdmins

Type: boolean

Default: false

Description: Whether to apply this check to organization administrators.

options.url

Type: string

Required: yes (if type is redirect or frame)

Description: User-facing URL for interaction.

The Auth Guard module acts as a security checkpoint within the authentication process. It is invoked after standard verification steps (Password, MFA, Device Trust) but before the user is granted access to the organization.

Depending on the configuration, the module can interact with the user in one of three ways:

  • Direct Type: Automatically approves or denies access via a backend API call.
  • Redirect Type: Redirects the user to an external page for verification.
  • Frame Type: Displays an embedded iframe for in-app verification.

The following diagram illustrates where the Auth Guard module fits into the user login flow:

User Login
Password Authentication
MFA Verification (if enabled)
Device Trust Verification (if enabled)
┌─────────────────────────┐
│ Auth Guard Module(s) │ ← Your App
└─────────────────────────┘
Remember Me Confirmation (if enabled)
Access Granted

Multiple Auth Guard modules can be configured per organization. They are executed sequentially, and all must pass for the user to gain access.

The Direct type is the simplest interaction method, ideal for automated checks that do not require user input (e.g., certificate validation). Crowdin makes a synchronous server-to-server API call to your app, which must respond immediately (< 10 seconds).

┌─────────────┐ ┌──────────────┐
│ Crowdin │ │ Your App │
└──────┬──────┘ └──────┬───────┘
│ │
│ POST /api/auth/verify │
│ Authorization: Bearer <JWT> │
│ { │
│ "userId": 12345, │
│ "organizationId": 67890, │
│ "ipAddress": "192.168.1.1", │
│ "moduleKey": "your-module-key" │
│ } │
├─────────────────────────────────────────────────>│
│ │
│ Process verification │
│ (IP check, etc.) │
│ │
│ { "success": true } │
│ or │
│ { │
│ "success": false, │
│ "message": "Access denied: IP not allowed" │
│ } │
│<─────────────────────────────────────────────────┤
│ │

HTTP request:

Terminal window
POST {AppBaseUrl}/api/auth/verify

Request Headers

The request to your app will contain the following headers:

  • Authorization: Bearer <JWT_TOKEN>
  • Content-Type: application/json

Request Payload Example:

{
"userId": 12345,
"organizationId": 67890,
"ipAddress": "192.168.1.1",
"moduleKey": "your-module-key"
}

The app must return a JSON object indicating success or failure.

Response Payload Example (Success):

{
"success": true
}

Response Payload Example (Failure):

{
"success": false,
"message": "Access denied: Your IP address is not in the allowlist"
}

The Redirect type routes the user to an external page for verification, then redirects them back to Crowdin. This type is suitable when user interaction is required (e.g., accepting terms, solving a CAPTCHA, or integrating with external OAuth/SAML providers).

┌─────────┐ ┌──────────────┐ ┌──────────┐
│ User │ │ Crowdin │ │ Your App │
└────┬────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1. Login attempt │ │
├───────────────────────────────────>│ │
│ │ │
│ │ Try POST /api/auth/verify │
│ │ (with empty body initially) │
│ ├─────────────────────────────────>│
│ │ │
│ │ { "success": false } │
│ │ (needs user interaction) │
│ │<─────────────────────────────────┤
│ │ │
│ 2. HTTP 302 Redirect │ │
│ Location: https://your-app/page │ │
│ ?jwtToken=<JWT>&state=<STATE> │ │
│<───────────────────────────────────┤ │
│ │ │
│ 3. GET /page?jwtToken=...&state=... │
├──────────────────────────────────────────────────────────────────────>│
│ │ │
│ │ Verify JWT token │
│ │ Show verification UI │
│ │ │
│ 4. Display verification page │
│<──────────────────────────────────────────────────────────────────────┤
│ │ │
│ 5. User completes verification │
│ (clicks approve/deny) │ │
├──────────────────────────────────────────────────────────────────────>│
│ │ │
│ │ Generate code │
│ │ │
│ 6. HTTP 302 Redirect back │
│ Location: https://accounts.../callback │
│ ?state=<STATE>&code=<CODE> │ │
│ or ...?state=<STATE>&error=... │ │
│<──────────────────────────────────────────────────────────────────────┤
│ │ │
│ 7. GET /callback?state=...&code=... │
├───────────────────────────────────>│ │
│ │ │
│ │ POST /api/auth/verify │
│ │ { │
│ │ "code": "<CODE>", │
│ │ "userId": ..., │
│ │ "organizationId": ..., │
│ │ "ipAddress": ..., │
│ │ "moduleKey": "..." │
│ │ } │
│ ├─────────────────────────────────>│
│ │ │
│ │ { "success": true } │
│ │<─────────────────────────────────┤
│ │ │
│ 8. Access granted │ │
│<───────────────────────────────────┤ │
│ │ │

Crowdin first attempts a direct check to see if access can be granted automatically.

HTTP request:

Terminal window
POST {AppBaseUrl}/api/auth/verify

Request Headers

The request to your app will contain the following headers:

  • Authorization: Bearer <JWT_TOKEN>
  • Content-Type: application/json

Request Payload Example:

{
"userId": 12345,
"organizationId": 67890,
"ipAddress": "192.168.1.1",
"moduleKey": "your-module-key"
}

Expected Response (Trigger Redirect):

To trigger the redirect flow, your app must return success: false.

{
"success": false
}

If the initial check returns false, Crowdin redirects the user to the url defined in your manifest options.

Redirect URL Structure:

Terminal window
https://{your-app-url}?jwtToken=<JWT>&state=<STATE>

Your app must validate the jwtToken, display the verification UI, and upon success, redirect the user back to Crowdin using an HTTP 302 redirect.

On Success:

HTTP 302 Redirect
Location: https://accounts.crowdin.com/{domain}/guard/callback?state=<STATE>&code=<CODE>

On Failure:

HTTP 302 Redirect
Location: https://accounts.crowdin.com/{domain}/guard/callback?state=<STATE>&error=User+denied+access

Once Crowdin receives the code from the callback, it sends a final request to your app to verify it.

HTTP request:

Terminal window
POST {AppBaseUrl}/api/auth/verify

Request Headers

  • Authorization: Bearer <JWT_TOKEN>
  • Content-Type: application/json

Request Payload Example:

{
"code": "abc123xyz",
"userId": 12345,
"organizationId": 67890,
"ipAddress": "192.168.1.1",
"moduleKey": "your-module-key"
}

Expected Response:

{
"success": true
}

The Frame type displays your verification page within an iframe inside the Crowdin login interface. This provides a seamless user experience for custom forms or mTLS checks while keeping the user within the Crowdin environment.

┌─────────┐ ┌──────────────┐ ┌──────────┐
│ User │ │ Crowdin │ │ Your App │
└────┬────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1. Login attempt │ │
├───────────────────────────────>│ │
│ │ │
│ │ Try POST /api/auth/verify │
│ ├─────────────────────────────────>│
│ │ │
│ │ { "success": false } │
│ │<─────────────────────────────────┤
│ │ │
│ 2. Show verification page │ │
│ with embedded iframe │ │
│<───────────────────────────────┤ │
│ │ │
│ 3. Iframe loads your URL │ │
│ GET /frame?jwtToken=... │ │
├──────────────────────────────────────────────────────────────────>│
│ │ │
│ 4. Your verification UI │ │
│<──────────────────────────────────────────────────────────────────┤
│ │ │
│ 5. User interacts with iframe │ │
│ (clicks approve/deny) │ │
│────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ 6. JavaScript API call via │ │ │
│ Crowdin Apps SDK │ │ │
│ AP.verifyAuth({ code: "..."}) │ │
├────────────────────────────────────────────────────────────────┼─>│
│ │ │ │
│ 7. Crowdin receives code │<──────────────────────────────┘ │
│ from iframe via postMessage│ │
│ │ │
│ │ POST /api/auth/verify │
│ │ { │
│ │ "code": "...", │
│ │ "userId": ..., │
│ │ "moduleKey": "..." │
│ │ } │
│ ├─────────────────────────────────>│
│ │ │
│ │ { "success": true } │
│ │<─────────────────────────────────┤
│ │ │
│ 8. Access granted │ │
│<───────────────────────────────┤ │
│ │ │

Crowdin first attempts a direct check to see if access can be granted automatically.

HTTP request:

Terminal window
POST {AppBaseUrl}/api/auth/verify

Request Payload Example:

{
"userId": 12345,
"organizationId": 67890,
"ipAddress": "192.168.1.1",
"moduleKey": "your-module-key"
}

Expected Response (Trigger Iframe):

To trigger the iframe flow, your app must return success: false.

{
"success": false
}

If the initial check returns false, Crowdin loads your app’s url in an iframe with the following parameters:

Terminal window
https://{your-app-url}?jwtToken=<JWT>

Verification UI Implementation:

Create an HTML page that initializes the Crowdin Apps SDK and handles the user interaction.

<!DOCTYPE html>
<html>
<head>
<title>Verification</title>
<script src="https://cdn.crowdin.com/apps/dist/host.js"></script>
</head>
<body>
<h1>Security Verification</h1>
<p>Please confirm your identity</p>
<button id="approve">Approve</button>
<button id="deny">Deny</button>
<script>
// Get parameters
const urlParams = new URLSearchParams(window.location.search);
const jwtToken = urlParams.get('jwtToken');
const state = urlParams.get('state');
document.getElementById('approve').addEventListener('click', async () => {
// Generate verification code from your backend
const code = await generateVerificationCode();
// Send success to Crowdin via SDK
AP.verifyAuth({
code: code
});
});
document.getElementById('deny').addEventListener('click', () => {
// Send rejection to Crowdin
AP.verifyAuth({
error: 'User denied access'
});
});
async function generateVerificationCode() {
// Call your backend to generate a code
const response = await fetch('/api/generate-code', {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwtToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ state })
});
const data = await response.json();
return data.code;
}
</script>
</body>
</html>

Use the AP.verifyAuth() method to communicate the result back to Crowdin.

Success:

AP.verifyAuth({
code: "your-verification-code"
});

Failure:

AP.verifyAuth({
error: "Verification failed: device not trusted"
});

Once Crowdin receives the code from the SDK, it sends a final request to your app to verify it.

HTTP request:

Terminal window
POST {AppBaseUrl}/api/auth/verify

Request Payload Example:

{
"code": "abc123xyz",
"userId": 12345,
"organizationId": 67890,
"ipAddress": "192.168.1.1",
"moduleKey": "your-module-key"
}

Expected Response:

{
"success": true
}
Was this page helpful?