Test your integration and go live
Test both the happy path and deliberate failures on stable HTTPS staging domains. The repository's tenant_system reference app includes controls for the core scenarios.
Test data
Prepare three identities:
| Identity | Nanaade state | Expected result |
|---|---|---|
| Returning pilot user | Active membership, matching subject | Successful 303 launch |
| First-time user | No Nanaade identity or membership | JIT user + membership creation, then successful 303 |
| Inactive user | Inactive/revoked target membership | SSO_MEMBERSHIP_INACTIVE |
| Conflicting user | Active member of another tenant | Branded transfer/conflict page |
Do not use real production credentials in local fixtures or automated test output.
Contract scenarios
| Scenario | How to produce it | Expected result |
|---|---|---|
| Valid | Fresh times, nonce, active member | Cookie + 303 to approved workspace |
| First launch | Fresh unknown sub and email with name | User and membership created once; cookie + 303 |
| Replay | Submit identical signed fields twice | Second returns 409 SSO_ASSERTION_REPLAYED |
| Expired | Set iat 10 minutes ago and exp 5 minutes ago | 401 SSO_ASSERTION_EXPIRED |
| Invalid signature | Sign with a different test secret | 401 SSO_SIGNATURE_INVALID |
| Future clock | Set iat more than 60 seconds ahead | 401 SSO_ASSERTION_EXPIRED |
| Bad origin | Submit from unregistered staging origin | 403 SSO_ORIGIN_NOT_ALLOWED |
| Bad destination | Sign unregistered redirect origin | 403 SSO_REDIRECT_NOT_ALLOWED |
| Missing launch code | Omit launchCode | 400 SSO_LAUNCH_CODE_REQUIRED |
| Unknown launch code | Sign admin.open or another invented value | 400 SSO_LAUNCH_CODE_INVALID |
| Unlicensed launch | Request a registered module the tenant does not own | 403 SSO_LAUNCH_FEATURE_NOT_LICENSED |
| Unsafe deep link | Put a path or URL in launchContext.contextId | 400 SSO_LAUNCH_CONTEXT_INVALID |
| Subject mismatch | Change sub after first binding | 409 SSO_SUBJECT_MISMATCH |
| Missing first-launch name | Unknown identity without name | 400 SSO_NAME_REQUIRED |
| Unsafe portal return | Sign an unregistered portal origin | 403 SSO_PORTAL_RETURN_NOT_ALLOWED |
| Cross-tenant identity | Use an email active under Tenant A to launch Tenant B | Conflict page; no duplicate active membership |
Generate destructive cases only in staging. Never intentionally expose or corrupt a production secret.
Authorization scenarios after SSO
- Licensed sidebar entries and dashboard cards appear.
- Unlicensed entries are omitted.
- Direct unlicensed page navigation renders Feature Unavailable.
- Direct API requests return
403 FEATURE_NOT_LICENSEDwith the required code. - Removing a parent module blocks dependent add-ons.
- Revoking membership blocks all subsequent tenant access.
- Tenant A cannot read Tenant B roster, analytics, uploads, notifications, or background-job output.
Usage and expiry scenarios
- Set a low Gemini tenant threshold and confirm only Gemini-backed write operations return
TENANT_USAGE_LIMIT_REACHED. - Confirm Job Board browsing, profile, tenant configuration, and SSO remain available.
- Set a per-member daily threshold and confirm another member is unaffected.
- Verify licensed calls create bypassed
FeatureUsagerecords without deducting retail credits. - Issue a short-lived test credential and confirm an expired exchange returns
SSO_CREDENTIAL_EXPIRED. - End a non-renewing test agreement and run the credential sweep; confirm tenant suspension, credential revocation, and audit events.
- Display an unlicensed navigation item and confirm it is disabled as an upgrade prompt while its direct route still returns Feature Unavailable.
Branding and hosted UI scenarios
- Verify the tenant display name, browser title, and favicon on the final tenant hostname.
- Test wide, square, and tall transparent logos in the desktop sidebar and mobile header.
- Confirm logos preserve aspect ratio and remain visible in specialized module sidebars.
- Verify primary buttons, selected navigation, focus rings, dialogs, and toast indicators use the tenant theme.
- Confirm destructive and warning states retain their semantic meaning and accessible contrast.
- Configure a dark logo and confirm it is used on dark surfaces.
- Change branding, refresh tenant configuration, and confirm the next workspace load uses the new version.
- Move between a tenant hostname and retail Nanaade; confirm branding does not leak across contexts.
Rotation rehearsal
Rotate with a short overlap. Confirm the new secret works immediately, old secret works only during overlap, and old secret fails afterward. Then update the operational runbook with the actual deployment time required.
Production checklist
- Production portal, workspace, and API use HTTPS.
- Exact origins and minimal return paths are registered.
- Production credential differs from staging.
- Secret is server-only and access-controlled.
- Assertions derive identity from authenticated server state.
- Servers use synchronized time.
- Handoff responses are no-store with restrictive CSP.
- Logs redact payloads, signatures, cookies, and personal data.
- First-time JIT provisioning creates exactly one user and membership.
- Returning launches reuse the subject-bound identity.
- Conflict and transfer-request flows preserve the original membership until approval.
- Every portal module button uses a registered launch code.
- Nanaade—not the assertion—selects the destination route and enforces licensing.
- Replay, expiry, signature, origin, redirect, and membership failures pass.
- Module, permission, and cross-tenant isolation tests pass.
- Rotation and emergency revocation are rehearsed.
- DNS, TLS, redirects, and cookies pass on final hostnames.
- Technical and incident contacts are documented.
What success looks like
The user clicks once in the tenant portal, briefly sees the secure handoff page, lands in the correctly branded Nanaade workspace, and sees only licensed capabilities. No Nanaade password is requested, no tenant secret reaches the browser, and repeated or altered assertions fail with stable diagnostic codes.