Backend Integration
Your backend server acts as a secure proxy between your frontend application and Redacto's API. This guide covers the required endpoints and security best practices.
Overview
Your backend needs to implement two key endpoints:
- Token Generation - Create JWT tokens for users
- Token Refresh - Renew expired tokens
These endpoints call Redacto's API using your CMS API Key and return tokens to your frontend.
Token Generation
Create an endpoint that generates JWT tokens for users.
Redacto API Endpoint
POST /consent/public/organisations/{organisation_uuid}/workspaces/{workspace_uuid}/tokens/generate
Request to Redacto
Headers:
X-CMS-API-KEY: {your-cms-api-key}
Content-Type: application/json
Body:
{
"email": "[email protected]",
"org_user_id": "user123",
"mobile": "+1234567890",
"expires_in_days": 1
}At least one of
org_user_id, ormobilemust be provided.Important: Token generation does not create consent users. Users are created when consent is submitted through the SDK, or when you explicitly call the User Management API. This allows you to generate tokens for anonymous IDs without creating user records until consent is actually given.
Response from Redacto
{
"detail": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2024-01-16T10:30:00Z"
}
}Token Refresh
Create an endpoint that refreshes expired access tokens.
Redacto API Endpoint
POST /consent/public/organisations/{organisation_uuid}/workspaces/{workspace_uuid}/tokens/refresh
Request to Redacto
Headers:
X-CMS-API-KEY: {your-cms-api-key}
Content-Type: application/json
Body:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}Response from Redacto
{
"detail": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": "2024-01-16T10:30:00Z"
}
}Note: Old tokens are invalidated when new tokens are generated.
Example Implementation
Express.js (Node.js)
import express, { Application, Request, Response } from "express";
import axios from "axios";
const app: Application = express();
app.use(express.json());
// Environment variables
const CMS_API_KEY = process.env.CMS_API_KEY;
const REDACTO_BASE_URL = process.env.REDACTO_BASE_URL || "https://api.redacto.io";
// Token generation endpoint
app.post(
"/api/consent/token",
async (req: Request, res: Response): Promise<void> => {
try {
const { email, org_user_id, mobile } = req.body;
const { organisation_uuid, workspace_uuid } = req.query;
if (!CMS_API_KEY) {
res.status(500).json({ error: "CMS API Key not configured" });
return;
}
if (!email && !org_user_id && !mobile) {
res.status(400).json({ error: "At least one user identifier required" });
return;
}
const response = await axios.post(
`${REDACTO_BASE_URL}/consent/public/organisations/${organisation_uuid}/workspaces/${workspace_uuid}/tokens/generate`,
{
email,
org_user_id,
mobile,
expires_in_days: 1,
},
{
headers: {
"X-CMS-API-Key": CMS_API_KEY,
"Content-Type": "application/json",
},
}
);
res.json({
token: response.data.detail.token,
refresh_token: response.data.detail.refresh_token,
expires_at: response.data.detail.expires_at,
});
} catch (error) {
console.error("Error generating token:", error);
res.status(500).json({ error: "Failed to generate token" });
}
}
);
// Token refresh endpoint
app.post(
"/api/consent/refresh-token",
async (req: Request, res: Response): Promise<void> => {
try {
const { refresh_token, organisation_uuid, workspace_uuid } = req.body;
if (!CMS_API_KEY) {
res.status(500).json({ error: "CMS API Key not configured" });
return;
}
const response = await axios.post(
`${REDACTO_BASE_URL}/consent/public/organisations/${organisation_uuid}/workspaces/${workspace_uuid}/tokens/refresh`,
{ refresh_token },
{
headers: {
"X-CMS-API-Key": CMS_API_KEY,
"Content-Type": "application/json",
},
}
);
res.json({
token: response.data.detail.token,
refresh_token: response.data.detail.refresh_token,
expires_at: response.data.detail.expires_at,
});
} catch (error) {
console.error("Error refreshing token:", error);
res.status(500).json({ error: "Failed to refresh token" });
}
}
);
app.listen(3000, () => {
console.log("Server running on port 3000");
});Python (FastAPI)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import os
app = FastAPI()
CMS_API_KEY = os.getenv("CMS_API_KEY")
REDACTO_BASE_URL = os.getenv("REDACTO_BASE_URL", "https://api.redacto.io")
class TokenRequest(BaseModel):
email: str | None = None
org_user_id: str | None = None
mobile: str | None = None
organisation_uuid: str
workspace_uuid: str
class RefreshRequest(BaseModel):
refresh_token: str
organisation_uuid: str
workspace_uuid: str
@app.post("/api/consent/token")
async def generate_token(request: TokenRequest):
if not CMS_API_KEY:
raise HTTPException(status_code=500, detail="CMS API Key not configured")
if not request.email and not request.org_user_id and not request.mobile:
raise HTTPException(status_code=400, detail="At least one user identifier required")
async with httpx.AsyncClient() as client:
response = await client.post(
f"{REDACTO_BASE_URL}/consent/public/organisations/{request.organisation_uuid}/workspaces/{request.workspace_uuid}/tokens/generate",
json={
"email": request.email,
"org_user_id": request.org_user_id,
"mobile": request.mobile,
"expires_in_days": 1,
},
headers={
"X-CMS-API-Key": CMS_API_KEY,
"Content-Type": "application/json",
},
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail="Failed to generate token")
data = response.json()["detail"]
return {
"token": data["token"],
"refresh_token": data["refresh_token"],
"expires_at": data["expires_at"],
}
@app.post("/api/consent/refresh-token")
async def refresh_token(request: RefreshRequest):
if not CMS_API_KEY:
raise HTTPException(status_code=500, detail="CMS API Key not configured")
async with httpx.AsyncClient() as client:
response = await client.post(
f"{REDACTO_BASE_URL}/consent/public/organisations/{request.organisation_uuid}/workspaces/{request.workspace_uuid}/tokens/refresh",
json={"refresh_token": request.refresh_token},
headers={
"X-CMS-API-Key": CMS_API_KEY,
"Content-Type": "application/json",
},
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail="Failed to refresh token")
data = response.json()["detail"]
return {
"token": data["token"],
"refresh_token": data["refresh_token"],
"expires_at": data["expires_at"],
}Django
import requests
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
import json
@csrf_exempt
def generate_token(request):
if request.method != "POST":
return JsonResponse({"error": "Method not allowed"}, status=405)
cms_api_key = settings.CMS_API_KEY
base_url = getattr(settings, "REDACTO_BASE_URL", "https://api.redacto.io")
if not cms_api_key:
return JsonResponse({"error": "CMS API Key not configured"}, status=500)
data = json.loads(request.body)
org_uuid = request.GET.get("organisation_uuid")
ws_uuid = request.GET.get("workspace_uuid")
if not data.get("email") and not data.get("org_user_id") and not data.get("mobile"):
return JsonResponse({"error": "At least one user identifier required"}, status=400)
response = requests.post(
f"{base_url}/consent/public/organisations/{org_uuid}/workspaces/{ws_uuid}/tokens/generate",
json={
"email": data.get("email"),
"org_user_id": data.get("org_user_id"),
"mobile": data.get("mobile"),
"expires_in_days": 1,
},
headers={
"X-CMS-API-Key": cms_api_key,
"Content-Type": "application/json",
},
)
if response.status_code != 200:
return JsonResponse({"error": "Failed to generate token"}, status=500)
detail = response.json()["detail"]
return JsonResponse({
"token": detail["token"],
"refresh_token": detail["refresh_token"],
"expires_at": detail["expires_at"],
})
@csrf_exempt
def refresh_token(request):
if request.method != "POST":
return JsonResponse({"error": "Method not allowed"}, status=405)
cms_api_key = settings.CMS_API_KEY
base_url = getattr(settings, "REDACTO_BASE_URL", "https://api.redacto.io")
if not cms_api_key:
return JsonResponse({"error": "CMS API Key not configured"}, status=500)
data = json.loads(request.body)
response = requests.post(
f"{base_url}/consent/public/organisations/{data['organisation_uuid']}/workspaces/{data['workspace_uuid']}/tokens/refresh",
json={"refresh_token": data["refresh_token"]},
headers={
"X-CMS-API-Key": cms_api_key,
"Content-Type": "application/json",
},
)
if response.status_code != 200:
return JsonResponse({"error": "Failed to refresh token"}, status=500)
detail = response.json()["detail"]
return JsonResponse({
"token": detail["token"],
"refresh_token": detail["refresh_token"],
"expires_at": detail["expires_at"],
})Security Best Practices
1. Protect Your API Key
- Never expose the CMS API Key to the frontend
- Store it in environment variables, not in code
- Use secrets management in production (e.g., AWS Secrets Manager, HashiCorp Vault)
# .env file (never commit to version control)
CMS_API_KEY=your-secret-key-here2. Validate User Identity (For User Management APIs)
User identity validation is not required for token generation - you can generate tokens for anonymous IDs, emails, or any identifier. However, you should validate user identity before calling User Management APIs (create, update, link users) to prevent unauthorized modifications:
// Token generation - no validation required (can use anonymous IDs)
app.post("/api/consent/token", async (req, res) => {
const { identifier } = req.body; // Could be anonymous ID, email, etc.
const response = await generateRedactoToken({
org_user_id: identifier,
});
res.json(response);
});
// User Management APIs - validate identity first
app.post("/api/consent/create-user", async (req, res) => {
// Verify user is authenticated before modifying user records
const authenticatedUser = await authenticateRequest(req);
if (!authenticatedUser) {
return res.status(401).json({ error: "Unauthorized" });
}
// Now safe to create/update/link consent users
const response = await createConsentUser({
org_user_id: authenticatedUser.id,
primary_email: authenticatedUser.email,
});
res.json(response);
});3. Secure Token Transmission
- Use HTTPS for all API calls
- Consider using httpOnly cookies for token storage
- Implement proper CORS configuration
import cors from "cors";
app.use(cors({
origin: ["https://your-frontend.com"],
credentials: true,
}));4. Handle Token Expiration
Implement automatic token refresh in your frontend:
async function fetchWithAuth(url: string, options: RequestInit = {}) {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${getAccessToken()}`,
},
});
if (response.status === 401) {
// Token expired, try to refresh
await refreshTokens();
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${getAccessToken()}`,
},
});
}
return response;
}5. Rate Limiting
Protect your token endpoints from abuse:
import rateLimit from "express-rate-limit";
const tokenLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
});
app.use("/api/consent/token", tokenLimiter);Updated 9 days ago