Security Best Practices
Use these practices when integrating third-party applications with Muhajir Studio in production.
Production Service Boundaries
| Service | Production URL | Security role |
|---|---|---|
| API | https://api.muhajirstudio.com/ | OIDC endpoints, token exchange, JWKS, public metadata, and external APIs. |
| Main web | https://login.muhajirstudio.com/ | Hosted user login, signup, email verification, and consent. |
| Admin web | https://admin.muhajirstudio.com/ | Application registration, scope selection, and RBAC-protected administration. |
Keep these services distinct in client configuration. External integrations should call only documented OIDC, public metadata, and /api/external/* endpoints.
Use Authorization Code Flow with PKCE
Admin-created applications are Public PKCE clients:
client_idis public.client_secretis not issued.code_challenge_methodmust beS256.- Plain PKCE challenges are disabled.
- Each login attempt must generate fresh
state,nonce, andcode_verifiervalues.
Validate state before exchanging the authorization code. Treat an invalid state as a failed login attempt and restart the flow.
Register Exact Redirects and Origins
Muhajir Studio derives redirect URIs from two registered values:
| Registered value | Example | Rules |
|---|---|---|
| Allowed web origin | https://app.example.com | Origin only; no path, query, fragment, or wildcard. |
| Redirect path | /auth/callback | Path only; no host, query, or fragment. |
The resulting redirect URI must match exactly, for example https://app.example.com/auth/callback.
Security recommendations:
- Use HTTPS origins in production.
- Register only origins you control.
- Keep callback paths dedicated to OAuth/OIDC handling.
- Do not accept callback URLs from untrusted request parameters.
- For mobile apps, use HTTPS Universal Links or Android App Links rather than custom URL schemes.
Request Least-Privilege Scopes
Admin-selected application scopes are the source of truth. During authorization, Muhajir Studio constrains the effective requested scope set to the application's registered scopes.
Use only scopes your integration actually needs:
| Scope | Gives access to |
|---|---|
openid | Stable subject identifier; required for /api/external/me. |
profile | Basic profile fields such as name and picture. |
email | Email address and verification status. |
permissions | Assigned permission keys where supported. |
offline_access | Refresh-token based sessions where supported. |
After a scope change, users must start a new authorization flow to receive tokens with the updated grants.
Validate Tokens Correctly
Use the right token for the right purpose:
| Token | Intended use |
|---|---|
| Access token | Send to /api/external/* with Authorization: Bearer ACCESS_TOKEN. |
| ID token | Validate user authentication claims in your client or backend. Do not send it to external APIs. |
| Refresh token | Store securely and use only for refreshing sessions. |
When validating ID tokens, check at least:
- issuer matches the production issuer
- audience matches your
client_id - expiration has not passed
- nonce matches the value created for the login attempt when you requested one
Fetch signing keys from the JWKS endpoint advertised by OIDC discovery instead of hardcoding keys.
Store Tokens by Application Type
| Application type | Recommended storage |
|---|---|
| Backend web app | Store tokens server-side; give the browser only your application session cookie. |
| Browser SPA | Prefer in-memory access-token storage; persistent browser storage is an explicit XSS trade-off. |
| Mobile app | Store tokens in platform secure storage such as Keychain or encrypted credential storage. |
Never log tokens, authorization codes, refresh tokens, or ID tokens. Redact them from analytics, crash reports, support tickets, and browser console output.
Use External APIs with Bearer Tokens
Third-party integrations should use /api/external/* as the stable external API namespace.
GET /api/external/me
Authorization: Bearer ACCESS_TOKEN
Cookie-only sessions are not accepted by /api/external/*. A missing, expired, or invalid bearer access token returns 401 UNAUTHORIZED. A valid token without required scopes returns 403 FORBIDDEN with missingScopes where available.
Understand Browser CORS Rules
Browser-based third-party calls are allowed only from registered application origins.
Dynamic third-party CORS applies to:
POST /api/auth/oauth2/tokenOPTIONS /api/auth/oauth2/token/api/external/*
It is intentionally non-credentialed:
| Setting | Value |
|---|---|
| Credentials | false |
| Methods | GET, POST, OPTIONS |
| Allowed headers | Authorization, Content-Type |
| Registered-origin cache | 60 seconds |
Do not use credentials: 'include' from third-party browser clients when calling token or external API endpoints. Send bearer tokens explicitly.
Protect Admin Access with Permissions
Admin surfaces are permission-protected. The admin API requires login-web-admin for admin-only namespaces and feature-specific permissions such as:
manage-usersmanage-rolesmanage-servicesmanage-authenticationmanage-brandingmanage-applications
Do not rely on UI hiding as a security boundary. Backend permission checks are the source of truth.
When editing a role assigned to your own account, the admin API rejects permission changes that would remove your own login-web-admin or manage-roles access. Use another administrator account for intentional admin-access transfers or emergency recovery.
Keep First-Party and Third-Party Flows Separate
First-party web applications can use cookie-backed helper endpoints where explicitly documented, such as the cookie token wrapper used by web-admin. Third-party integrations should use the standard OIDC token endpoint and bearer-token external APIs unless instructed otherwise.
This separation prevents browser integrations from depending on internal cookie-session assumptions.
Security Checklist
- Use HTTPS for all production redirect origins.
- Use Authorization Code Flow with PKCE and
S256. - Generate fresh
state,nonce, and PKCE values for every login. - Validate
stateand, when using ID tokens, validatenonce. - Register exact origins and redirect paths only.
- Request least-privilege scopes.
- Store tokens according to application type.
- Send access tokens only in
Authorization: Bearerheaders. - Do not log credentials, tokens, authorization codes, or refresh tokens.
- Use backend permissions, not frontend visibility, for admin enforcement.