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.
{ "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: Required: yes Description: Module identifier within the Crowdin app. |
name | Type: Required: yes Description: Human-readable name displayed to users during verification. |
description | Type: Required: no Description: Additional description shown to users. Available only for the |
url | Type: Required: yes Description: Endpoint URL for verification. Relative to |
options | Type: Required: no Description: Module configuration options. |
options.type | Type: Allowed values: Default: Description: The verification interaction type. |
options.applyToAdmins | Type: Default: Description: Whether to apply this check to organization administrators. |
options.url | Type: Required: yes (if type is Description: User-facing URL for interaction. |
Communication between Auth Guard App and Crowdin
Section titled “Communication between Auth Guard App and Crowdin”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 GrantedMultiple 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:
POST {AppBaseUrl}/api/auth/verifyRequest 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:
POST {AppBaseUrl}/api/auth/verifyRequest 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:
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 RedirectLocation: https://accounts.crowdin.com/{domain}/guard/callback?state=<STATE>&code=<CODE>On Failure:
HTTP 302 RedirectLocation: https://accounts.crowdin.com/{domain}/guard/callback?state=<STATE>&error=User+denied+accessOnce Crowdin receives the code from the callback, it sends a final request to your app to verify it.
HTTP request:
POST {AppBaseUrl}/api/auth/verifyRequest 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:
POST {AppBaseUrl}/api/auth/verifyRequest 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:
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:
POST {AppBaseUrl}/api/auth/verifyRequest Payload Example:
{ "code": "abc123xyz", "userId": 12345, "organizationId": 67890, "ipAddress": "192.168.1.1", "moduleKey": "your-module-key"}Expected Response:
{ "success": true}