Rudra Documentation

Rudra is a managed authentication platform powered by Keycloak. It gives you Clerk-level features — user management, SSO, organizations, webhooks, RBAC, analytics, and coupons — all running on your own infrastructure.

🔐 Full Auth Stack

Email/password, social login, magic links, MFA, SAML & OIDC enterprise SSO — all configurable per project.

🏢 B2B Organizations

Multi-tenant orgs with member roles, invitations, and domain-based auto-join for enterprise customers.

🎟️ Coupon System

Create discount codes with plan restrictions, usage limits, and expiry. Applied during project creation.

📊 Analytics & Webhooks

User signup trends, login tracking, webhook delivery to your apps, and a full activity audit log.

Quick Start

Rudra runs entirely via Docker Compose. One command spins up 6 services:

git clone <your-repo>
cd keycloak-saas
docker compose up --build

Wait approximately 2 minutes for all services to initialize (Keycloak takes the longest). Then open:

ServiceURLPurpose
Dashboardhttp://localhost:3000Your Rudra control panel
API Docshttp://localhost:8000/docsInteractive Swagger API docs
Keycloakhttp://localhost:8080Keycloak admin console (admin / admin)
💡 Tip There are no default credentials for the Rudra dashboard. Click "Create one" on the login page to register your first admin account.

Architecture

┌──────────────────────────────────────────────────────┐ │ Docker Compose │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ React UI │───▶│ FastAPI │───▶│ Keycloak │ │ │ │ :3000 │ │ :8000 │ │ :8080 │ │ │ └──────────┘ └────┬─────┘ └──────┬───────┘ │ │ │ │ │ │ ┌────────────┼─────────────────┤ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │ │ MongoDB │ │ Redis │ │ PostgreSQL│ │ │ │ :27017 │ │ :6379 │ │ :5432 │ │ │ └──────────┘ └──────────┘ └───────────┘ │ └──────────────────────────────────────────────────────┘

Keycloak

IAM engine handling realms, users, authentication flows, sessions, identity providers, and token issuance.

PostgreSQL

Keycloak's persistent store for realm configs, users, credentials, and sessions.

MongoDB

Tenant metadata, organizations, invitations, webhooks, coupons, analytics events, and activity logs.

Redis

Session caching, rate limiting, and webhook delivery queues.

FastAPI

Management API layer (50+ endpoints) wrapping Keycloak Admin API with plan enforcement and business logic.

React + Nginx

Dashboard frontend served via nginx, which also proxies /api/ requests to FastAPI.

1. Create Your Account

When you open the dashboard at http://localhost:3000, you'll see the login screen.

1

Click "Create one"

At the bottom of the login form, click the signup link.

2

Fill in your details

Enter your full name, email, company name (optional), and a password (minimum 8 characters).

3

You're in!

You'll be redirected to the Dashboard with your stats overview. This is your platform admin account — it manages all your authentication projects.

ℹ️ Platform Admin vs End Users Your Rudra account is a platform admin account — you use it to manage projects. Your app's end users sign in through Keycloak's login pages, not the Rudra dashboard.

2. Create a Project

A "project" in Rudra maps to a Keycloak realm. Each project is an isolated authentication environment for one of your applications.

1

Select a Plan

Choose from Free, Pro ($25/mo), Business ($99/mo), or Enterprise ($499/mo). Each plan determines user limits, features, and quotas. You can upgrade later.

2

Name Your Project

Enter a display name (e.g. "My SaaS App") and a realm identifier (auto-generated, e.g. my-saas-app). The realm ID is used in your Keycloak auth URLs.

3

Apply a Coupon (optional)

If you have a discount code, enter it in the coupon field. It validates in real-time and the Order Summary sidebar shows your discounted price.

4

Click "Create Project"

Rudra provisions a new Keycloak realm with default auth settings, and you're taken to the project management page.

💡 What happens behind the scenes Creating a project calls the Keycloak Admin API to provision a new realm with password policies, brute force protection (5 failures → 15-min lockout), event logging enabled, and 30-day event retention.

Your Auth URL

After creation, your users will authenticate at:

http://localhost:8080/realms/{your-realm-id}/protocol/openid-connect/auth
  ?client_id=YOUR_CLIENT_ID
  &response_type=code
  &redirect_uri=http://localhost:3001/callback

3. Manage Users

Click into your project and select the Users tab. Here you can create, search, view, impersonate, and delete users.

Create a User

Click "+ Add User" and fill in:

Username:   janedoe
Email:      [email protected]
First Name: Jane
Last Name:  Doe
Password:   ••••••••

The user is created in Keycloak and a user.created webhook is fired.

User Actions

ActionDescriptionPlan
👁️ ViewSee user details, active sessions, assigned rolesAll
🔑 ImpersonateLog in as this user for debuggingPro+
🚪 Revoke SessionsForce logout from all devicesAll
🗑️ DeletePermanently remove user and all their sessionsAll

Search & Filter

Use the search bar to filter by username, email, first name, or last name. The search queries Keycloak's user API directly.

4. Set Up SSO

The SSO tab lets you add social login providers and enterprise SAML connections.

Add Google Login

1

Get Google OAuth credentials

Go to Google Cloud Console → Create OAuth 2.0 Client ID. Set the redirect URI to: http://localhost:8080/realms/{realm}/broker/google/endpoint

2

Add in Rudra

Click "+ OIDC Provider" → Select "Google" → Enter your Client ID and Client Secret → Click "Add Provider"

Add GitHub Login

Same flow as Google. Create an OAuth App at GitHub Developer Settings with callback URL: http://localhost:8080/realms/{realm}/broker/github/endpoint

Add Enterprise SAML

Business+ Click "+ SAML Provider" and provide:

Alias:               okta-saml
Entity ID:           https://www.okta.com/exk123abc
SSO URL:             https://company.okta.com/app/abc/sso/saml
Signing Certificate: (paste X.509 cert from IdP)

5. Organizations (B2B)

Pro+ Organizations enable multi-tenant B2B features. Each org has members with roles and optional domain-based auto-join.

Create an Organization

Name:                 Acme Corp
Slug:                 acme-corp      (auto-generated)
Auto-join Domains:    acme.com, subsidiary.com

Any user whose email ends in @acme.com can auto-join this org.

Invite Members

Click "Invite", enter an email, select the target org, and choose a role (admin or member). Invitations expire in 7 days.

Organization via API

Create Organization
POST /api/tenants/{realm}/organizations
{
  "name": "Acme Corp",
  "slug": "acme-corp",
  "allowed_email_domains": ["acme.com"]
}
Add Member
POST /api/tenants/{realm}/organizations/acme-corp/members
{
  "user_id": "keycloak-user-uuid",
  "role": "admin"
}

6. Roles & RBAC

The Roles tab lets you create custom roles and assign them to users. Roles are stored as Keycloak realm roles.

Create a Role

POST /api/tenants/{realm}/roles
{
  "name": "editor",
  "description": "Can edit content but not manage users"
}

Assign Role to User

POST /api/tenants/{realm}/users/{user_id}/roles/editor

Roles appear in the user's JWT token, so your app can check permissions:

// In your app's JWT payload:
{
  "realm_access": {
    "roles": ["editor", "default-roles-my-app"]
  }
}
ℹ️ Role Limits by Plan Free: 2 roles max · Pro: 20 roles · Business/Enterprise: Unlimited

7. Webhooks

Pro+ Webhooks send real-time HTTP notifications to your app when events occur.

Create a Webhook

In the Webhooks tab, click "Add Endpoint" and select the events you want:

EventWhen Fired
user.createdNew user registered or created via API
user.updatedUser profile changed
user.deletedUser removed
organization.createdNew org created
organizationMembership.createdMember added to org
session.revokedUser session terminated
invitation.createdNew invitation sent

Webhook Payload

POST https://your-app.com/webhooks
Headers:
  Content-Type: application/json
  X-Webhook-Secret: a1b2c3...your-secret...

Body:
{
  "type": "user.created",
  "data": {
    "username": "janedoe",
    "email": "[email protected]",
    "user_id": "kc-uuid-here"
  }
}

Verify Webhooks

Each webhook endpoint gets a unique secret (shown once at creation). Verify it in your app:

# Python / FastAPI
@app.post("/webhooks")
async def handle_webhook(request: Request):
    secret = request.headers.get("X-Webhook-Secret")
    if secret != os.environ["RUDRA_WEBHOOK_SECRET"]:
        raise HTTPException(401, "Invalid secret")
    body = await request.json()
    print(f"Event: {body['type']}, Data: {body['data']}")
    return {"ok": True}

8. Coupons & Discounts

Create discount codes that customers can apply when creating projects. Accessible from the Coupons page in the sidebar.

Create a Coupon

1

Navigate to Coupons

Click "Coupons" in the sidebar, then "New Coupon".

2

Configure the coupon

Set code (e.g. WELCOME50), discount %, max redemptions, valid plans, and optional expiry.

Coupon Fields

FieldDescriptionExample
CodeUnique coupon code (auto-uppercased)STARTUP30
Discount %Percentage off (1–100)30
Max RedemptionsUsage limit (-1 = unlimited)100
Valid PlansRestrict to specific plans (empty = all)pro, business
Expires InDays until expiry (0 = never)90

How Customers Use Coupons

When creating a project, there's a "Have a coupon code?" field on step 2. The code validates in real-time and the Order Summary shows the discount:

Plan:           Business        $99/mo
Coupon:         STARTUP30       -30%
─────────────────────────────────────
Total:                          $69/mo
                          You save $30/mo

Track Redemptions

Click the 👁️ icon on any coupon to see who redeemed it, on which project, and when.

9. Project Settings

The Settings tab in each project controls authentication behavior, branding, and plan management.

Authentication Toggles

SettingDescriptionPlan
Password AuthenticationEnable/disable email + password sign-inAll
Social LoginEnable Google, GitHub, etc.All
Magic LinksPasswordless email loginPro+
Multi-Factor AuthRequire MFA for all usersAll
Block Disposable EmailsReject mailinator, tempmail, etc.Pro+
Password Breach DetectionCheck against known breachesPro+
Bot ProtectionRate limiting & CAPTCHAPro+

Branding

Set a primary color (used in Keycloak login themes) and a logo URL per project.

Plan Management

Upgrade or downgrade your project's plan directly from Settings. Changes take effect immediately.

10. Analytics Dashboard

Pro+ The project Overview tab shows key metrics:

User Signups

Daily bar chart showing new user registrations over the last 30 days.

Login Activity

Total successful logins and failed login attempts from Keycloak events.

Resource Counts

Users, applications, SSO providers, organizations, roles, webhooks — at a glance.

Activity Log

Platform-wide audit trail on the Dashboard: who did what, when, on which project.

Connect Your Application

After creating a project and configuring auth, you need to register your app as an OIDC client and point your frontend to Keycloak.

Step 1: Register an Application

Go to your project → Applications tab → Click "+ Add Application":

Client ID:      my-web-app
Protocol:       OpenID Connect
Redirect URIs:  http://localhost:3001/callback

Step 2: Configure Your Frontend

Use any OIDC client library. Here's the configuration your app needs:

{
  "authority":     "http://localhost:8080/realms/YOUR_REALM",
  "client_id":     "my-web-app",
  "redirect_uri":  "http://localhost:3001/callback",
  "response_type": "code",
  "scope":         "openid profile email"
}

Step 3: Verify Tokens

Your backend validates JWTs using Keycloak's public key:

# JWKS endpoint (auto-discovered):
http://localhost:8080/realms/YOUR_REALM/protocol/openid-connect/certs

Code Examples

React (oidc-client-ts)

import { UserManager } from 'oidc-client-ts';

const userManager = new UserManager({
  authority: 'http://localhost:8080/realms/my-app',
  client_id: 'my-web-app',
  redirect_uri: 'http://localhost:3001/callback',
  response_type: 'code',
  scope: 'openid profile email',
});

// Login
userManager.signinRedirect();

// Callback page
const user = await userManager.signinRedirectCallback();
console.log('Token:', user.access_token);
console.log('Roles:', user.profile.realm_access?.roles);

Python Backend (JWT Verification)

import jwt
import requests

KEYCLOAK_URL = "http://localhost:8080/realms/my-app"

# Fetch Keycloak public key
jwks = requests.get(f"{KEYCLOAK_URL}/protocol/openid-connect/certs").json()

def verify_token(token: str):
    header = jwt.get_unverified_header(token)
    key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])
    public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
    return jwt.decode(token, public_key, algorithms=["RS256"],
                      audience="my-web-app")

Node.js / Express

const { Issuer } = require('openid-client');

async function setup() {
  const keycloak = await Issuer.discover(
    'http://localhost:8080/realms/my-app'
  );
  const client = new keycloak.Client({
    client_id: 'my-web-app',
    redirect_uris: ['http://localhost:3001/callback'],
    response_types: ['code'],
  });

  // Generate login URL
  const url = client.authorizationUrl({ scope: 'openid profile email' });

  // Exchange code for tokens
  const tokenSet = await client.callback(
    'http://localhost:3001/callback',
    { code: req.query.code }
  );
  console.log('Access Token:', tokenSet.access_token);
}

Webhook Handler (Python)

from fastapi import FastAPI, Request, HTTPException
import os

app = FastAPI()

@app.post("/webhooks/rudra")
async def webhook(request: Request):
    # Verify the webhook secret
    secret = request.headers.get("X-Webhook-Secret")
    if secret != os.environ["WEBHOOK_SECRET"]:
        raise HTTPException(401)

    event = await request.json()

    if event["type"] == "user.created":
        # Provision user in your database
        print(f"New user: {event['data']['email']}")

    elif event["type"] == "organization.created":
        # Set up tenant workspace
        print(f"New org created")

    return {"received": True}

API Reference

All endpoints require a Bearer token (from /api/auth/login) except register and login. Full interactive docs at http://localhost:8000/docs.

Authentication

POST/api/auth/registerCreate admin account
POST/api/auth/loginLogin, returns JWT
GET/api/auth/meCurrent admin profile

Projects (Tenants)

POST/api/tenantsCreate project (accepts coupon_code)
GET/api/tenantsList all projects
GET/api/tenants/{realm}Project details with counts
PUT/api/tenants/{realm}Update plan or name
DELETE/api/tenants/{realm}Delete project and realm
PUT/api/tenants/{realm}/auth-settingsToggle auth features
PUT/api/tenants/{realm}/brandingUpdate logo/colors

Users

POST/api/tenants/{realm}/usersCreate user
GET/api/tenants/{realm}/usersList/search users
GET/api/tenants/{realm}/users/{uid}User detail + sessions + roles
PUT/api/tenants/{realm}/users/{uid}Update user
DELETE/api/tenants/{realm}/users/{uid}Delete user
POST/api/tenants/{realm}/users/{uid}/impersonateImpersonate user (Pro+)

Sessions

GET/api/tenants/{realm}/users/{uid}/sessionsList active sessions
DELETE/api/tenants/{realm}/users/{uid}/sessionsRevoke all sessions
DELETE/api/tenants/{realm}/sessions/{sid}Revoke specific session

Organizations

POST/api/tenants/{realm}/organizationsCreate org (Pro+)
GET/api/tenants/{realm}/organizationsList orgs
POST/api/tenants/{realm}/organizations/{slug}/membersAdd member
DELETE/api/tenants/{realm}/organizations/{slug}/members/{uid}Remove member
DELETE/api/tenants/{realm}/organizations/{slug}Delete org

SSO / Identity Providers

POST/api/tenants/{realm}/idp/oidcAdd OIDC provider (Google, GitHub, etc.)
POST/api/tenants/{realm}/idp/samlAdd SAML provider (Business+)
GET/api/tenants/{realm}/idpList providers
DELETE/api/tenants/{realm}/idp/{alias}Delete provider

Roles

POST/api/tenants/{realm}/rolesCreate role
GET/api/tenants/{realm}/rolesList roles
POST/api/tenants/{realm}/users/{uid}/roles/{name}Assign role to user
DELETE/api/tenants/{realm}/users/{uid}/roles/{name}Remove role from user

Applications (Clients)

POST/api/tenants/{realm}/clientsRegister OIDC/SAML client
GET/api/tenants/{realm}/clientsList clients
DELETE/api/tenants/{realm}/clients/{id}Delete client

Webhooks

POST/api/tenants/{realm}/webhooksCreate endpoint (Pro+)
GET/api/tenants/{realm}/webhooksList endpoints
GET/api/tenants/{realm}/webhooks/{wid}/logsDelivery logs
DELETE/api/tenants/{realm}/webhooks/{wid}Delete endpoint

Coupons

POST/api/couponsCreate coupon
GET/api/couponsList your coupons
POST/api/coupons/validateValidate a code
GET/api/coupons/{code}/redemptionsView redemptions
PUT/api/coupons/{code}/toggleEnable/disable
DELETE/api/coupons/{code}Delete coupon

Analytics & Dashboard

GET/api/tenants/{realm}/analyticsSignups, logins, daily trends
GET/api/tenants/{realm}/eventsRaw Keycloak events
GET/api/dashboardPlatform overview
GET/api/plansAvailable plans with features
GET/api/healthHealth check

Plans & Limits

Feature
Free
Pro
Business
Enterprise
Price
$0
$25
$99
$499
Users
10K
100K
500K
Projects
1
5
Organizations
50
SAML SSO
3
Webhooks
3
10
Custom Roles
2
20
Analytics
User Impersonation
Breach Detection
Bot Protection
Block Disposable Emails
Magic Links
API Rate Limit
100/m
1K/m
5K/m
Coupons

Troubleshooting

"Backend service unavailable" on login

The backend hasn't started yet. Wait 1-2 minutes after docker compose up for Keycloak to finish initializing. Check with:

docker compose ps           # All services should show "healthy"
docker compose logs backend # Check for Python errors

"Internal Server Error" on register

Check docker compose logs backend for the actual Python traceback. Common causes:

  • MongoDB connection failed — wait for MongoDB healthcheck to pass
  • Dependency issue — rebuild with docker compose build --no-cache backend

Keycloak takes forever to start

Keycloak 24 on first boot imports its database schema which takes 60-120 seconds. The backend won't start until Keycloak's healthcheck passes. Subsequent restarts are much faster.

Port conflicts

If ports 3000, 5432, 6379, 8000, 8080, or 27017 are in use, stop conflicting services or edit docker-compose.yml ports.

Clean restart

docker compose down -v      # Stop everything and delete data volumes
docker compose up --build   # Rebuild and start fresh

Check API directly

# Health check
curl http://localhost:8000/api/health

# Register via CLI
curl -X POST http://localhost:8000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"test1234","name":"Test"}'

# Login
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"test1234"}'