Authentication Flows
QNSP supports multiple authentication flows depending on identity type.
Authentication Flows
QNSP supports multiple authentication flows depending on identity type.
User authentication
Password flow
POST /auth/login
{
"email": "user@example.com",
"tenantId": "<tenant_uuid>",
"password": "..."
}
Returns access token + refresh token.
WebAuthn flow
POST /auth/webauthn/authenticate/start— get challenge- Client signs with authenticator
POST /auth/webauthn/authenticate/complete— verify and get tokens
OAuth / Social Sign-In
One-click sign-up and sign-in via GitHub, Google, Microsoft, GitLab, or Bitbucket. Handled entirely by the Cloud Portal BFF — no direct auth-service API calls required from the client.
- User navigates to
GET /api/auth/oauth/{provider} - BFF generates a CSRF state (HMAC-SHA256 nonce) and sets
qnsp_oauth_statecookie (HttpOnly, SameSite=Lax, 10 min TTL) - For authenticated linking flows, the BFF also sets short-lived intent cookies so the callback knows whether this is:
- a login/signup flow, or
- a link-external-identity flow from profile settings
- BFF redirects to the provider's authorization endpoint:
- GitHub:
https://github.com/login/oauth/authorize— scopesuser:email read:user - Google:
https://accounts.google.com/o/oauth2/v2/auth— scopesopenid email profile - Microsoft:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize - GitLab:
https://gitlab.com/oauth/authorize - Bitbucket:
https://bitbucket.org/site/oauth2/authorize
- GitHub:
- Provider redirects to
GET /api/auth/oauth/{provider}/callback?code=…&state=… - BFF verifies the CSRF state against the cookie, exchanges the
codefor an access token, and fetches the user profile from the provider API - Returning user: identity lookup via
GET /auth/oauth/identity→ session issued - New user: tenant + user provisioned via billing-service, identity linked via
POST /auth/oauth/identity, then session issued - Authenticated link flow: the provider identity is bound to the current QNSP user without provisioning a new tenant or rotating the active session
- BFF calls
POST /auth/oauth/sessionfor login/signup flows → receives PQC-signed JWT (ML-DSA) + refresh token - Session cookies written; user redirected to
/dashboardor back to/profile?tab=accountsfor link flows
Environment variables (Cloud Portal):
| Variable | Description |
|---|---|
CLOUD_OAUTH_GITHUB_CLIENT_ID |
GitHub OAuth app client ID |
CLOUD_OAUTH_GITHUB_CLIENT_SECRET |
GitHub OAuth app client secret |
CLOUD_OAUTH_GITHUB_CALLBACK_URL |
Optional GitHub callback override |
CLOUD_OAUTH_GOOGLE_CLIENT_ID |
Google OAuth app client ID |
CLOUD_OAUTH_GOOGLE_CLIENT_SECRET |
Google OAuth app client secret |
CLOUD_OAUTH_GOOGLE_CALLBACK_URL |
Optional Google callback override |
CLOUD_OAUTH_MICROSOFT_CLIENT_ID |
Microsoft OAuth app client ID |
CLOUD_OAUTH_MICROSOFT_CLIENT_SECRET |
Microsoft OAuth app client secret |
CLOUD_OAUTH_MICROSOFT_CALLBACK_URL |
Optional Microsoft callback override |
CLOUD_OAUTH_GITLAB_CLIENT_ID |
GitLab OAuth app client ID |
CLOUD_OAUTH_GITLAB_CLIENT_SECRET |
GitLab OAuth app client secret |
CLOUD_OAUTH_GITLAB_CALLBACK_URL |
Optional GitLab callback override |
CLOUD_OAUTH_BITBUCKET_CLIENT_ID |
Bitbucket OAuth app client ID |
CLOUD_OAUTH_BITBUCKET_CLIENT_SECRET |
Bitbucket OAuth app client secret |
CLOUD_OAUTH_BITBUCKET_CALLBACK_URL |
Optional Bitbucket callback override |
CLOUD_OAUTH_SESSION_SECRET |
HMAC secret for CSRF state signing |
CLOUD_PORTAL_URL |
Callback base URL (default: https://cloud.qnsp.cuilabs.io) |
Sign up / sign in at: cloud.qnsp.cuilabs.io/auth
Enterprise Federation (OIDC / SAML)
QNSP supports workforce federation for:
- Microsoft Entra ID
- Okta
- Auth0
- Google Workspace
- AWS IAM Identity Center
- Tenant-configured SAML 2.0 or OIDC providers
Flow:
- User selects Continue with your company SSO
- QNSP performs tenant discovery by verified email domain first, then tenant/workspace identifier
- If multiple workforce providers exist for the tenant, QNSP presents a provider selector instead of guessing
- QNSP starts the provider-specific OIDC or SAML flow
- Auth-service validates the callback/assertion and either:
- issues QNSP tokens for sign-in, or
- links the external identity to the currently authenticated QNSP user when the flow was initiated from profile settings
Linked External Identities
Authenticated users can manage linked identities from Profile → Linked Accounts in the Cloud Portal.
Supported linking paths:
- Social OAuth: GitHub, Google, Microsoft, GitLab, Bitbucket
- Workforce SSO: Entra ID, Okta, Auth0, Google Workspace, AWS IAM Identity Center, and configured tenant SAML/OIDC providers
Unlinking removes the external identity binding without deleting the QNSP user account or tenant membership.
Service authentication
Service token flow
POST /auth/service-token
Authorization: Bearer <service-secret>
{
"serviceId": "<uuid>",
"audience": "internal-service"
}
Returns access token only (no refresh).
Token refresh
POST /auth/token/refresh
{
"refreshToken": "<token>"
}
Returns new access token + rotated refresh token.
Organization access (C2)
Request-to-join (public)
Users who are not yet members can request access through edge-gateway:
POST /public/join-requests
{
"tenant": "<tenant_slug_or_uuid>",
"email": "user@company.com",
"requestedRole": "Developer"
}
Invite acceptance (password reset)
Invites are implemented as a locked user created in auth-service, followed by a password reset email.
POST /auth/forgot-passwordsends reset emailPOST /auth/reset-passwordsets the new password
On first successful reset, auth-service activates invited users:
locked→active