Smartflow lets your users authenticate with the identity system they already have — Entra ID, SAML, Kerberos, or a corporate proxy — and carries their identity, groups, and role into every AI request. No extra accounts. No API key distribution.
When a user calls the Smartflow proxy, they include whatever token their corporate identity
provider already issued — an OIDC id_token from Entra ID, a SAML assertion
from ADFS, a Kerberos SPNEGO ticket from AD, or simply trusted headers injected by your
existing reverse proxy. Smartflow validates the token cryptographically, extracts the
user's identity and group membership, and issues a short-lived
Smartflow-internal JWT. All subsequent AI calls carry that JWT, allowing
the policy engine to enforce per-user and per-team rules without the user ever
registering a Smartflow account.
The Smartflow JWT contains sub (short username), email,
groups (comma-separated), department, roles
(admin or user), and auth_method so every
trace and log entry is attributed to a real person, not a shared key.
| Method | Best for | Client sends | Config needed | Complexity |
|---|---|---|---|---|
| OIDC / Entra ID | Azure AD / Microsoft 365 orgs, Okta, any OIDC IdP | Authorization: Bearer <id_token> |
Tenant ID, Client ID, JWKS URI | Low |
| SAML 2.0 | ADFS, legacy enterprise, Entra SAML apps | X-SAML-Assertion: <b64> |
IdP metadata URL or signing cert | Medium |
| Kerberos SPNEGO | On-prem AD, intranet deployments | Authorization: Negotiate <spnego> |
Service principal, keytab path | Medium |
| Trusted Proxy Headers | Nginx/F5/Zscaler already handling SSO upstream | x-remote-user: jsmith |
Trusted IP CIDR list | Zero |
This is the recommended method for most enterprise customers. Your Entra ID (Azure AD)
tenant issues an id_token JWT when a user signs in to your app. You forward
that token to Smartflow, which validates the signature against the Entra JWKS endpoint,
checks the iss, aud, and exp claims, then extracts
the user's identity and group memberships.
Username from email → upn → preferred_username (in priority order, @ suffix stripped).
Group GUIDs or CNs from the groups claim.
Display name from name. Department from department.
The aud and iss claims are strictly validated.
Step 1 — Register a Smartflow app in Entra ID
In the Azure Portal → App registrations → New registration:
Smartflow Proxyhttps://<your-domain>/api/auth/sso/callbackgroups (security groups)Step 2 — Configure via the Dashboard UI
Navigate to your Smartflow dashboard → SSO Configuration (or go directly to /dashboard/sso_config.html):
Select Microsoft Entra ID from the Identity Provider dropdown.
Enter your Tenant ID (e.g. contoso.onmicrosoft.com or the GUID).
Enter your Client ID from the app registration.
The JWKS URI auto-fills to https://login.microsoftonline.com/<tenant>/discovery/v2.0/keys.
Set Redirect URI to https://<your-smartflow-domain>/api/auth/sso/callback.
Click Save Configuration, then Test Connection to verify.
Step 2 (alternative) — Configure via CLI / curl
# POST your SSO config to the API (use your admin key) curl -X POST https://<your-domain>/api/auth/sso/config \ -H "Authorization: Bearer <admin-key>" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "contoso.onmicrosoft.com", "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "jwks_uri": "https://login.microsoftonline.com/contoso.onmicrosoft.com/discovery/v2.0/keys" }' # Verify the config was stored curl https://<your-domain>/api/auth/sso/config \ -H "Authorization: Bearer <admin-key>"
Step 3 — Exchange a token
# Your app gets an id_token from Entra ID via MSAL/ADAL, then: curl -X POST https://<your-domain>/api/auth/sso/signin \ -H "Authorization: Bearer <entra-id-token>" # Response: { "token": "eyJhbGci...", // Smartflow JWT — use for all AI calls "expires_in": 3600, "token_type": "Bearer", "user": { "username": "jsmith", "email": "jsmith@contoso.com", "groups": ["Engineering", "AI-Users"], "auth_method": "oidc:EntraID" } }
Step 4 — Use the Smartflow JWT for AI calls
# All AI requests now go through the policy engine with jsmith's identity
curl -X POST https://<your-domain>/v1/chat/completions \
-H "Authorization: Bearer <smartflow-jwt>" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'
Every AI call is attributed to jsmith and his groups.
MAESTRO policy engine can enforce team-specific model allow-lists, token budgets,
and guardrail profiles. Audit logs show real usernames. If Engineering
is in the admin_groups list, jsmith gets admin access automatically.
Browser Login Flow (Dashboard) — for human users signing into the Smartflow dashboard:
/dashboard/sso_login.htmlGET /api/auth/sso/login which builds the Entra ID authorize URL using the stored config and redirects the browser./api/auth/sso/callback.GET /api/auth/sso/callback?code=... exchanges the authorization code for an id_token using OIDC_CLIENT_SECRET, validates it, and mints the Smartflow JWT.localStorage. All subsequent dashboard API calls use it.Required environment variables on the container (for the auth-code flow only):
OIDC_CLIENT_SECRET=<your-app-secret-from-azure> OIDC_REDIRECT_URI=https://<your-domain>/api/auth/sso/callback
If your organisation uses Active Directory Federation Services (ADFS) or has a SAML
enterprise app in Entra ID, your app can POST the base64-encoded SAML assertion
directly to Smartflow. Smartflow fetches the IdP signing certificate from the
federation metadata XML, verifies the RSA-SHA256 signature, checks NotOnOrAfter
expiry, and extracts the NameID, email, groups, and department from
well-known SAML attribute URNs.
Configure via CLI
# ADFS federation metadata URL curl -X POST https://<your-domain>/api/auth/sso/config \ -H "Authorization: Bearer <admin-key>" \ -H "Content-Type: application/json" \ -d '{ "saml_idp_metadata_url": "https://adfs.contoso.com/FederationMetadata/2007-06/FederationMetadata.xml", "saml_sp_entity_id": "https://smartflow.contoso.com" }'
Exchange a SAML assertion
# Get a SAML assertion from ADFS (your SAML SP logic does this) # Then pass it to Smartflow in the X-SAML-Assertion header: curl -X POST https://<your-domain>/api/auth/sso/signin \ -H "X-SAML-Assertion: <base64-encoded-saml-assertion>"
Smartflow recognises standard SAML attribute URNs automatically:
...claims/emailaddress, ...claims/upn, displayName,
memberOf, department, and the Microsoft groups claim
...ws/2008/06/identity/claims/groups.
Any attribute containing "group" or "role" in its name is also captured.
Domain-joined Windows machines already hold a Kerberos Ticket Granting Ticket (TGT)
from AD logon. When a Kerberos-aware client application calls Smartflow, it presents a
SPNEGO token in Authorization: Negotiate <token>. Smartflow
decodes the ASN.1 GSSAPI wrapper, extracts the client principal
(e.g. jsmith@CONTOSO.COM), strips the realm, and returns a Smartflow JWT.
An optional LDAP enrichment step can add group memberships and department.
Configure via config.yaml (server-side)
sso:
kerberos:
enabled: true
service_principal: "HTTP/smartflow.contoso.com@CONTOSO.COM"
keytab_path: "/etc/smartflow/smartflow.keytab"
admin_groups:
- "Domain Admins"
- "Smartflow-Admins"
Client call (browser or app with SSPI/GSSAPI)
# Windows PowerShell — browser does this automatically on intranet
$token = [System.Convert]::ToBase64String(
(New-Object System.Net.Http.HttpClient).DefaultRequestHeaders.GetValues("Authorization")
)
curl -X POST https://smartflow.contoso.com/api/auth/sso/signin `
-H "Authorization: Negotiate $token"
Once configured, domain users on the corporate network authenticate silently — no username/password prompt. The browser or client app negotiates Kerberos transparently. After the Smartflow JWT is issued, group enrichment via LDAP attributes the request to the correct team's policy profile.
If your infrastructure already has an SSO-aware reverse proxy sitting in front of
Smartflow (e.g. Nginx with auth_request, an F5 APM, Zscaler App Connector,
or AWS ALB with OIDC), configure Smartflow to trust identity headers injected by that
proxy. No tokens are validated by Smartflow — it trusts the upstream proxy
completely, but only from IPs in your trusted CIDR list.
Configure via config.yaml (or environment variable)
sso:
trusted_proxy_cidrs:
- "10.0.0.0/8" # internal corporate network
- "172.16.0.0/12" # private range
- "192.168.0.0/16" # private range
Your upstream proxy injects these headers
# Nginx example — after auth_request validates the session cookie: proxy_set_header x-smartflow-user-id $authenticated_user; proxy_set_header x-smartflow-user-email $user_email; proxy_set_header x-smartflow-user-groups $user_groups; # comma-separated proxy_set_header x-smartflow-user-department $department;
Supported header names (checked in priority order)
# User identity (any of these) x-smartflow-user-id | x-remote-user | x-forwarded-user | x-authenticated-user | x-username # Supplementary attributes x-smartflow-user-email # or x-user-email x-smartflow-user-groups # comma-separated CNs — or x-user-groups x-smartflow-user-department # or x-user-department x-user-name # or x-display-name
Headers from IPs not in trusted_proxy_cidrs are
silently stripped and ignored — clients cannot impersonate other users by injecting
these headers themselves. Keep this CIDR list tight. If the list is empty, the
proxy-header path is disabled entirely.
After any SSO validation path succeeds, Smartflow inspects the user's group list
against the configured admin_groups patterns. Members of an admin group
receive roles ["admin","user"]; everyone else gets ["user"].
Patterns support * wildcards.
Configure admin groups via the Dashboard UI
In SSO Configuration → Auto-provisioned Teams defaults section → enter the group names in the Admin Groups field (one per line, wildcards supported).
Configure via CLI
# Override default admin groups [Smartflow-Admins, IT-Admins, Domain Admins] curl -X POST https://<your-domain>/api/auth/sso/config \ -H "Authorization: Bearer <admin-key>" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "contoso.onmicrosoft.com", "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "admin_groups": ["IT-Admins", "AI-Platform-Admins", "*-SuperAdmin"] }'
Default admin groups (if not overridden):
"Smartflow-Admins" | "IT-Admins" | "Domain Admins"
When Sync groups on every login is enabled, the user's group membership
is re-evaluated every time they exchange a token. If someone is removed from an
AD group, their next sign-in reflects the new role — no manual admin action needed.
The Traces dashboard shows the auth_method and groups for every request.
Your company has 200 developers on Entra ID. You want all Cursor or VS Code Copilot traffic routed through Smartflow, with each developer's AI usage tracked and their team's budget enforced — without issuing 200 API keys.
Setup
OIDC_CLIENT_SECRET and OIDC_REDIRECT_URI on the container./dashboard/sso_login.html → click Sign in with Microsoft → Microsoft MFA completes → browser redirected to /api/auth/sso/callback → Smartflow JWT returned.https://<domain>/v1, API Key = their Smartflow JWT. All completions flow through Smartflow under their identity.Engineering get GPT-4 + Claude access. Contractors in External-AI-Limited are restricted to GPT-4o-mini with a lower monthly token budget. All enforced automatically from their AD group.
You have an internal web application that calls an LLM API. The app sits behind an
Nginx reverse proxy that already validates SAML sessions and injects
x-remote-user. You want AI calls attributed to real users without
changing the app at all.
Nginx config snippet
# Nginx already handles auth_request — add these lines to your location block location /v1/ { auth_request /auth-validate; auth_request_set $authed_user $upstream_http_x_auth_user; proxy_pass http://smartflow:7775; proxy_set_header x-remote-user $authed_user; proxy_set_header x-user-groups $authed_groups; # set by your auth module } # Smartflow trusts this Nginx IP — config.yaml: sso: trusted_proxy_cidrs: ["10.1.0.0/16"] # IP of Nginx
No changes to the app. Nginx injects the headers → Smartflow reads them → every AI call is logged under the real user's name with their team's policy applied.
Zero application code changes. Users are attributed instantly. Full audit trail in Traces dashboard. Per-team model restrictions and budget controls activate based on the groups Nginx passes through.
A Python microservice runs as a service principal in Entra ID. It uses the client-credentials flow to get an access token, then exchanges it for a Smartflow JWT once per hour and reuses it for all LLM calls during that period.
## Python example — service principal OIDC token exchange import msal, openai, time # 1. Acquire Entra ID token via client credentials app = msal.ConfidentialClientApplication( client_id="<app-client-id>", client_credential="<app-secret>", authority="https://login.microsoftonline.com/<tenant-id>" ) result = app.acquire_token_for_client(scopes=["api://<app-client-id>/.default"]) entra_token = result["access_token"] # 2. Exchange for Smartflow JWT import requests resp = requests.post( "https://<your-domain>/api/auth/sso/signin", headers={"Authorization": f"Bearer {entra_token}"} ) sf_token = resp.json()["token"] expires_at = time.time() + resp.json()["expires_in"] - 60 # 1 min buffer # 3. Use for all OpenAI-compatible calls through Smartflow client = openai.OpenAI( api_key=sf_token, base_url="https://<your-domain>/v1" ) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Summarise this contract..."}] )
Check current SSO config
curl https://<your-domain>/api/auth/sso/config \ -H "Authorization: Bearer <admin-key>" # Returns: { "success": true, "config": { "tenant_id": "...", "client_id": "..." } } # If not configured: { "success": false, "error": "Not configured" }
Generate an SSO login URL (test the config is wired correctly)
curl https://<your-domain>/api/auth/sso/login # Returns: { "auth_url": "https://login.microsoftonline.com/..." } # Paste the auth_url in a browser — should redirect to Microsoft login
Test token exchange manually
# Decode any Smartflow JWT to inspect claims (no secret needed for base64): echo "eyJhbGci..." | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool # Should show: sub, username, email, groups, roles, auth_method, exp
Common issues
| Error | Cause | Fix |
|---|---|---|
Token rejected by all OIDC providers |
Wrong aud or iss in token |
Ensure client_id matches the app registration. Check the token's aud claim matches exactly. |
SAML assertion expired |
Clock skew or stale assertion | SAML assertions are short-lived. Re-acquire a fresh one. Check server NTP sync. |
Proxy headers from untrusted IP — ignored |
Source IP not in trusted_proxy_cidrs |
Add the upstream proxy IP/CIDR to trusted_proxy_cidrs in config. |
JWKS fetch error |
JWKS URI unreachable from container | Verify network egress from the container to login.microsoftonline.com. Check firewall/proxy rules. |
| Groups not appearing in JWT | Entra ID app not configured to emit group claims | In Azure Portal → App registration → Token configuration → Add optional claim → groups (Security groups). For large directories, consider Graph API enrichment. |
Not configured from config endpoint |
SSO config not saved to Redis | POST the config via the dashboard UI or curl. Verify Redis connectivity on the api-server container. |