Kolya BR Proxy uses OAuth exclusively for authentication (no local username/password). Two OAuth providers are supported:
- AWS Cognito (default/recommended) -- AWS-managed user pool authentication
- Microsoft Entra ID (Azure AD) -- personal and enterprise Microsoft accounts
Cognito is the default provider selected during deployment via deploy-all.sh. Both providers follow the Authorization Code flow with state-based CSRF protection. OAuth state is persisted in the database and validated on callback.
Related docs:
- Security Design — CORS, CSRF, token security, RBAC, and detailed login flow analysis
- Deployment Guide — Infrastructure setup including OAuth environment variables
When you deploy with deploy-all.sh, Cognito is fully configured automatically — no manual AWS Console steps needed:
deploy-all.sh Step 1 (Terraform) Step 1 Post-apply Step 2-4
──────────────────────────────── ───────────────────── ─────────
Creates: Reads Terraform outputs: ExternalSecret syncs
• Cognito User Pool • user_pool_id SM → k8s Secret
• App Client (confidential) • app_client_id → Pod env vars:
• Hosted UI domain • app_client_secret KBR_COGNITO_*
• Callback URLs (from tfvars) Pushes to Secrets Manager
• Password policy Creates first admin user
• Pre-signup Lambda (email filter) (prompts for email)
What happens during deployment:
- Terraform creates all resources — User Pool, App Client (authorization code grant,
openid+profile+emailscopes), hosted UI domain, callback URLs (derived fromfrontend_domaininterraform.tfvars), password policy, and a pre-signup Lambda that validates email domains. - Credentials are pushed to Secrets Manager —
deploy-all.shreadscognito_user_pool_id,cognito_app_client_id, andcognito_app_client_secretfrom Terraform outputs and writes them to the project's Secrets Manager secret. - First admin user is created — The script detects an empty user pool and prompts you for an admin email. It calls
admin-create-user, and Cognito emails the temporary password. - ExternalSecret Operator syncs to pods — In k8s, the
ExternalSecretresource pulls credentials from Secrets Manager every hour and mounts them asKBR_COGNITO_USER_POOL_ID,KBR_COGNITO_CLIENT_ID,KBR_COGNITO_CLIENT_SECRETenvironment variables.
After deployment, you only need to:
- Check your email for the temporary password
- Log in and set a permanent password (min 8 chars, uppercase + lowercase + number + symbol)
No manual Cognito configuration is needed.
If you are setting up Cognito manually (e.g. using an existing User Pool or a non-Terraform deployment):
Click to expand manual setup steps
- In the AWS Console > Cognito, create a new User Pool (or use an existing one).
- Note the User Pool ID (format:
us-west-2_AbCdEfGhI).
- Under your User Pool, go to App integration > App client > Create app client.
- Select Confidential client (server-side).
- Set:
- App client name:
Kolya BR Proxy - Generate client secret: Yes
- Allowed callback URLs:
http://localhost:3000/auth/cognito/callback- For production, add
https://<your-domain>/auth/cognito/callback
- For production, add
- Allowed sign-out URLs:
http://localhost:3000 - OAuth 2.0 grant types: Authorization code grant
- OpenID Connect scopes:
openid,profile,email
- App client name:
- Note the Client ID and Client Secret.
- Under App integration > Domain, set a Cognito domain prefix or custom domain.
- The OAuth endpoints derive from the User Pool ID:
- Authorize:
https://<pool-id-suffix>.auth.<region>.amazoncognito.com/oauth2/authorize - Token:
https://<pool-id-suffix>.auth.<region>.amazoncognito.com/oauth2/token - UserInfo:
https://<pool-id-suffix>.auth.<region>.amazoncognito.com/oauth2/userInfo
- Authorize:
KBR_COGNITO_USER_POOL_ID=us-west-2_EXAMPLE
KBR_COGNITO_CLIENT_ID=<your-cognito-client-id>
KBR_COGNITO_CLIENT_SECRET=<your-cognito-client-secret>
KBR_COGNITO_REGION=us-west-2
KBR_COGNITO_REDIRECT_URIS=http://localhost:3000/auth/cognito/callback
KBR_COGNITO_REGIONdefaults toKBR_AWS_REGIONif not set.
Self-registration is disabled. All users must be created by an administrator.
Via Admin Dashboard (Recommended)
- Log in as a super_admin or admin
- Go to Admin Users page
- Click Invite Admin
- Fill in email, username, temporary password, role, and permissions
- The invited user receives login instructions and sets a permanent password on first login via the Cognito hosted UI
This is the recommended approach — it creates the Cognito user and sets up role/permissions in a single step.
Alternative: via AWS CLI or Console
Create user via AWS CLI
Important: The user pool uses email as an alias, so
--usernamemust not be an email address. Use a plain username (e.g. the part before@), and pass the email via--user-attributes.
aws cognito-idp admin-create-user \
--user-pool-id us-west-2_AbCdEfGhI \
--username jdoe \
--user-attributes Name=email,Value=jdoe@example.com Name=email_verified,Value=true \
--desired-delivery-mediums EMAIL \
--region us-west-2Cognito sends a temporary password to the user's email. On first login, the user will be prompted to set a permanent password (minimum 8 characters, uppercase, lowercase, numbers, and symbols).
To skip the force-change-password flow, set a permanent password directly:
aws cognito-idp admin-set-user-password \
--user-pool-id us-west-2_AbCdEfGhI \
--username jdoe \
--password 'PermanentPass123!' \
--permanent \
--region us-west-2Create user via AWS Console
- Go to AWS Console > Cognito > User Pools
- Select your user pool
- Go to Users tab > Create user
- Fill in email and temporary password
- The user logs in and sets a permanent password on first login
Note: Users created via CLI or Console still need to be assigned a role/permissions in the Admin Dashboard after their first login.
- Start the backend and frontend.
- Visit the login page and click the Cognito login option (this is the default provider).
- Complete the Cognito hosted UI login flow.
sequenceDiagram
participant F as Frontend
participant B as Backend
participant C as Cognito
F->>B: GET /admin/auth/cognito/login?redirect_uri=...
B->>B: Generate & persist state
B-->>F: {authorization_url}
F->>C: Redirect to Cognito hosted UI
C-->>F: Redirect with code & state
F->>B: POST /admin/auth/cognito/callback?code=...&state=...
B->>B: Validate state
B->>C: Exchange code for tokens
C-->>B: access_token
B->>C: GET /oauth2/userInfo
C-->>B: User profile
B->>B: Find/create user, issue JWT
B-->>F: {access_token, refresh_token, user}
Microsoft OAuth requires manual registration in Azure Portal (unlike Cognito which Terraform creates automatically). However, deploy-all.sh handles injecting the credentials into your cluster:
Azure Portal (manual) deploy-all.sh Kubernetes
───────────────────── ────────────── ──────────
App Registration → --configure auth → AWS Secrets Manager
• Client ID (interactive prompt) → ExternalSecret
• Client Secret → backend-secrets
• Tenant ID → Pod env vars
- Go to Azure Portal > App registrations and click New registration.
- Fill in:
- Name:
Kolya BR Proxy - Supported account types: "Accounts in any organizational directory and personal Microsoft accounts" (multi-tenant)
- Redirect URI: Platform
Web, URIhttps://<your-frontend-domain>/auth/microsoft/callback- For local dev, also add
http://localhost:3000/auth/microsoft/callback
- For local dev, also add
- Name:
- Click Register.
- On the Overview page, note:
- Application (client) ID
- Directory (tenant) ID
- Go to Certificates & secrets > New client secret.
- Set description (e.g.
Kolya BR Proxy Secret) and expiry (up to 24 months). - Click Add and immediately copy the Value (shown only once).
- Go to API permissions > Add a permission > Microsoft Graph > Delegated permissions.
- Add:
openid,profile,email,User.Read,GroupMember.Read.All. - Click Add permissions.
- Click Grant admin consent for [your tenant] — this is required for
GroupMember.Read.All(used by Entra ID group sync).
Important: Without admin consent for
GroupMember.Read.All, Microsoft will return a 403 error during the OAuth login flow when group sync is enabled.
Option A: Via deploy-all.sh (Recommended for production)
./deploy-all.sh --configure auth
# Select "1) Add/Update Microsoft Entra ID (SSO)"
# Enter: Client ID, Client Secret, Tenant ID
# Script writes to Secrets Manager → ExternalSecret syncs to podThe script will also offer to restart the backend pod to pick up the new secrets.
Option B: Local development (environment variables)
KBR_MICROSOFT_CLIENT_ID=<your-microsoft-client-id>
KBR_MICROSOFT_CLIENT_SECRET=<your-microsoft-client-secret>
KBR_MICROSOFT_TENANT_ID=common
KBR_MICROSOFT_REDIRECT_URIS=http://localhost:3000/auth/microsoft/callbackTenant ID options:
| Value | Supported Accounts |
|---|---|
common |
All Microsoft accounts (personal + enterprise) |
organizations |
Enterprise accounts only |
consumers |
Personal Microsoft accounts only |
<tenant-id> |
Specific organization only |
- Start the backend and frontend (or verify the deployed cluster).
- Visit the login page and click "Sign in with Microsoft".
- Complete the Microsoft login flow. The account is created (or linked) automatically.
sequenceDiagram
participant F as Frontend
participant B as Backend
participant M as Microsoft
F->>B: GET /admin/auth/microsoft/login?redirect_uri=...
B->>B: Generate & persist state
B-->>F: {authorization_url}
F->>M: Redirect to Microsoft login
M-->>F: Redirect with code & state
F->>B: POST /admin/auth/microsoft/callback?code=...&state=...
B->>B: Validate state
B->>M: Exchange code for tokens
M-->>B: access_token
B->>M: GET /me (user profile)
M-->>B: User profile
opt Group Sync Enabled
B->>M: GET /me/memberOf
M-->>B: Security group IDs
B->>B: Resolve role/permissions from group mappings
end
B->>B: Find/create user, issue JWT
B-->>F: {access_token, refresh_token, user}
See also: Security Design — Entra ID Group-Based Access Control for the detailed login flow diagram, fail-closed design rationale, and bootstrap security analysis.
Entra ID Group Sync maps Azure AD security groups to roles and permissions in the system. When enabled, user access is controlled by group membership rather than manual invitations.
- User logs in via Microsoft OAuth
- Backend calls Microsoft Graph API
/me/memberOfto get the user's security group memberships - Groups are matched against the
entra_group_mappingstable (highest priority wins) - The matching group determines the user's role and permissions (overwritten on every login)
- If no group matches → 403 denied
- If Graph API fails (network error, 401, 429, 500) → 503 denied (fail closed)
When KBR_MICROSOFT_ENABLE_GROUP_SYNC=true but no group mappings exist in the database and no Microsoft users exist yet, the very first Microsoft login automatically receives super_admin. This solves the chicken-and-egg problem.
The bootstrap window closes immediately — once the first Microsoft user exists in the DB, subsequent logins are rejected with "Group mappings not configured" until the super_admin creates mappings via the Entra Groups page.
Important: Do NOT enable group sync and share the login URL until you're ready to be the first person to log in. The first login claims the bootstrap super_admin slot.
-
Enable group sync (environment variable or configmap):
KBR_MICROSOFT_ENABLE_GROUP_SYNC=true
-
Create security groups in Azure Portal:
- Go to Azure Portal > Groups > New group
- Type: Security
- Add members who should have access
-
Configure group mappings in the admin dashboard:
- Navigate to Entra Groups in the sidebar
- Click Add Mapping
- Fill in:
- Entra Group ID: The Azure group's Object ID (found in Azure Portal > Groups > [group] > Overview)
- Group Name: Display name
- Role:
super_adminoradmin - Permissions: (for admin role) which resources the group can manage
- Priority: Higher number wins when a user belongs to multiple groups
| Scenario | Result |
|---|---|
Group sync disabled (false) |
All Microsoft users get admin role (legacy behavior) |
| Group sync enabled, no mappings, no MS users | First user gets super_admin (bootstrap) |
| Group sync enabled, no mappings, MS users exist | Login denied — 403 "Group mappings not configured" |
| Group sync enabled, mappings configured | User must be in a mapped group to login |
| User in multiple mapped groups | Highest priority group's role/permissions apply |
| User not in any mapped group | Login denied — 403 "Not authorized" |
| Graph API unreachable / returns error | Login denied — 503 "Unable to verify group membership" |
| User deactivated in KBP | Login denied — 403 regardless of group membership |
| Microsoft user's role edited in UI | Not possible — edit button disabled when group sync active |
| Variable | Required | Default | Description |
|---|---|---|---|
KBR_MICROSOFT_ENABLE_GROUP_SYNC |
No | false |
Enable Entra ID group-to-permission mapping |
| Variable | Required | Default | Description |
|---|---|---|---|
KBR_COGNITO_USER_POOL_ID |
For Cognito | -- | Cognito User Pool ID |
KBR_COGNITO_CLIENT_ID |
For Cognito | -- | Cognito app client ID |
KBR_COGNITO_CLIENT_SECRET |
For Cognito | -- | Cognito app client secret |
KBR_COGNITO_REGION |
No | KBR_AWS_REGION |
Cognito region |
KBR_COGNITO_REDIRECT_URIS |
No | http://localhost:3000/auth/cognito/callback |
Allowed redirect URIs (comma-separated) |
KBR_MICROSOFT_CLIENT_ID |
For MS OAuth | -- | Microsoft app client ID |
KBR_MICROSOFT_CLIENT_SECRET |
For MS OAuth | -- | Microsoft app client secret |
KBR_MICROSOFT_TENANT_ID |
No | common |
Azure AD tenant ID |
KBR_MICROSOFT_REDIRECT_URIS |
No | http://localhost:3000/auth/microsoft/callback |
Allowed redirect URIs (comma-separated) |
KBR_MICROSOFT_ENABLE_GROUP_SYNC |
No | false |
Enable Entra ID group-based access control |
- Rotate client secrets regularly (every 6-12 months).
- Store secrets in environment variables or a secrets manager -- never commit them to Git.
- Use Azure Key Vault / AWS Secrets Manager in production for secret storage.
- Restrict redirect URIs to only trusted domains.
- Use HTTPS in production for all OAuth redirect URIs.
| Problem | Solution |
|---|---|
| Redirect URI mismatch | Ensure the URI registered in Azure/Cognito matches exactly (including trailing slashes, protocol, port) |
| Invalid client secret | Secret may be expired -- regenerate in Azure Portal / Cognito console |
| Insufficient permissions | Verify openid, profile, email, GroupMember.Read.All scopes are granted with admin consent |
| Microsoft login returns 403 (before callback) | GroupMember.Read.All needs admin consent — go to App Registration > API permissions > Grant admin consent |
| Microsoft callback returns 403 "not authorized" | User is not in any mapped Entra group (when group sync is enabled) |
| Microsoft callback returns 403 "Group mappings not configured" | Group sync enabled but no mappings created yet, and bootstrap slot already taken |
| Microsoft callback returns 503 "Unable to verify group membership" | Graph API call to /me/memberOf failed — check network, token scopes, admin consent |
| Microsoft user's role reverts after manual edit | Expected — group sync overwrites role on every login; edit the group mapping instead |
| Cognito authorize request canceled | Check that KBR_COGNITO_DOMAIN matches the actual Cognito domain (verify with aws cognito-idp describe-user-pool --query UserPool.Domain) |
| Cognito callback URL mismatch | Ensure https://<your-domain>/auth/cognito/callback is added to the Cognito app client's allowed callback URLs |
| Cognito OAuth not configured (501) | Check that KBR_COGNITO_USER_POOL_ID, KBR_COGNITO_CLIENT_ID, and KBR_COGNITO_CLIENT_SECRET are all set |
| Microsoft OAuth not configured (501) | Check that KBR_MICROSOFT_CLIENT_ID and KBR_MICROSOFT_CLIENT_SECRET are set |