White-label Credentials
If you’re building a product on top of MIOSA (an AI coding agent, a vertical SaaS, an agency tool) and your end-users create sandboxes through your UI, MIOSA Security supports them as first-class identities - even though they don’t have MIOSA accounts.
This page covers:
- Attributing sandboxes to your end-users with
external_user_id - Storing credentials scoped to those end-users
- Branding the OAuth consent flow as your own product
- Auditing per end-user, per workspace
Background - external attribution
Every Computer and Sandbox already supports three caller-supplied attribution fields:
| Field | Purpose |
|---|---|
external_user_id | Your end-user’s identity in your system |
external_workspace_id | Your workspace/team grouping |
external_project_id | Your project identity |
These are labels, not authentication boundaries. But MIOSA Security uses them as credential-scoping keys, which means you can store secrets that resolve only for that end-user’s resources.
See External Attribution for the broader picture.
Stamping attribution at create time
When your backend creates a sandbox for one of your end-users, pass the attribution:
The values you choose are opaque to MIOSA - they’re whatever IDs make sense in your system.
Storing a credential scoped to an end-user
Suppose Dr. Smith pastes her own OpenAI key into your UI. Save it scoped to her:
client.secrets.set(
name="openai_key",
value="sk-proj-her-personal-key...",
scope="external_user",
external_user_id="dr_smith@clinic1.example",
expose_as_env="OPENAI_API_KEY",
) Now any sandbox you create with external_user_id="dr_smith@clinic1.example" resolves OPENAI_API_KEY to her key. Other clinicians at the same clinic do not see it.
For a clinic-wide credential (e.g., shared EHR access):
client.secrets.set(
name="ehr_api_key",
value="...",
scope="external_workspace",
external_workspace_id="clinic1",
expose_as_env="EHR_API_KEY",
) How the proxy decides which credential to use
When a sandbox makes an outbound call and the proxy needs to swap a placeholder, it walks (in priority order):
1. Direct binding on this specific sandbox
2. Secret scoped to user (owner_user_id - for MIOSA-native users)
3. Secret scoped to external_user (external_user_id match)
4. Secret scoped to external_workspace (external_workspace_id match)
5. Secret scoped to MIOSA workspace
6. Tenant-wide secret First match wins. If nothing matches, the placeholder stays in the request and the upstream call fails (the right behavior - better than silently leaking).
White-label OAuth (your branding, not MIOSA’s)
By default, when one of your end-users clicks “Connect GitHub” in your UI, they’re sent to GitHub with the consent screen showing “Authorize MIOSA”. For a true white-label experience, override that:
1. Register your own OAuth app on the provider
For GitHub:
https://github.com/settings/applications/new
Application name: CliniCIQ Cloud
Homepage URL: https://cliniciq.example
Authorization callback URL: https://app.cliniciq.example/oauth/callback 2. Drop the credentials into MIOSA
In your tenant admin dashboard → Settings → OAuth Providers → “Add provider override”:
Provider: github
Display name: GitHub
Client ID: <from GitHub>
Client Secret: <from GitHub>
Callback URL: https://app.cliniciq.example/oauth/callback
Branding: { "button_label": "Connect GitHub to CliniCIQ" } From now on, when one of your end-users connects GitHub, they see “Authorize CliniCIQ Cloud” - your name, your branding, your callback domain.
3. Make sure your callback proxies to MIOSA
Your https://app.cliniciq.example/oauth/callback route receives the OAuth code parameter and forwards it to MIOSA’s callback endpoint:
@app.get("/oauth/callback")
def oauth_callback(request):
code = request.query_params["code"]
state = request.query_params["state"]
# Forward to MIOSA
return miosa.oauth.complete(provider="github", code=code, state=state) MIOSA exchanges the code for tokens on your behalf and stores them in the vault.
Per-user auditing
Audit log carries the full identity chain on every row, so you can query by your own user IDs:
# All activity by one end-user
events = client.audit.list(
since="7d",
external_user_id="dr_smith@clinic1.example",
)
# All activity in one workspace
events = client.audit.list(
since="30d",
external_workspace_id="clinic1",
) Same filters available in the UI.
Privacy boundary
What a tenant admin (you, as the white-label operator) can do:
- ✅ See that a particular
external_user_idhas secrets bound to them - ✅ Read audit rows showing which credential resolved for each call
- ✅ Disable a secret to immediately revoke access
- ❌ Re-read the encrypted value of any stored secret
The “you can’t read stored values” rule applies to every actor in MIOSA - including the platform engineers. If one of your end-users ever asks “can CliniCIQ see my OpenAI key after I save it” - the answer is “no, the value is encrypted; only the request body that leaves your sandbox ever contains it, briefly, in transit.”
Disabling end-user-scoped credentials entirely
Some white-label customers want all credentials managed centrally - end-users can’t bring their own keys. Toggle this in your tenant settings:
Settings → Security → "Allow end-user-scoped credentials" → Off When off, only tenant and external_workspace scoped secrets resolve. End-user-scoped secret creation returns a 403.
Real-world example - ClinicIQ: AI medical assistant per clinician
ClinicIQ is a healthcare SaaS built on MIOSA. Each clinician gets their own sandbox. They supply their own OpenAI key through the ClinicIQ settings page. The audit log is surfaced in ClinicIQ’s compliance dashboard. GitHub OAuth shows “Authorize ClinicIQ” not “Authorize MIOSA.”
Troubleshooting
Sandbox can’t find the secret I stored for an end-user - env var is empty. The sandbox must have been created with the same external_user_id you used for the secret. Attribution is stamped at creation time and cannot be changed. Verify with client.sandboxes.get(sandbox_id).external_user_id.
scope="external_user" returns HTTP 403. Your tenant has “Allow end-user-scoped credentials” disabled. Enable it in Settings → Security, or use scope="external_workspace" for workspace-level credentials instead.
The resolver skips my workspace-scoped secret. The sandbox must have been created with a matching external_workspace_id. If the sandbox was created without it, the workspace-level scope walk entry is blank and won’t match.
OAuth consent screen still says “Authorize MIOSA” after I added a provider override. Double-check that the client_id in your override matches the OAuth app you registered with the provider, not MIOSA’s default client ID. Remove and re-add the override if needed.
My end-user can see other users’ sandboxes in the MIOSA dashboard. End-users should not have MIOSA accounts or API keys. If they do, create a browser token scoped to that user instead: client.browser_tokens.create(external_user_id="..."). Browser tokens are read-only and scoped to that user’s resources.
For more issues, see the Security troubleshooting hub.
Frequently asked
My end-users don’t have MIOSA accounts. Can they still use OAuth Connect? Yes. The OAuth flow attributes the resulting token to whatever external_user_id your backend passes when initiating the flow.
Can the same external_user_id exist in multiple tenants? Yes - external_user_id is scoped within a tenant. The same string in two different tenants represents two independent identities.
What if an end-user is in two of my workspaces? You can create credentials at the workspace scope for each - the proxy resolves the correct one based on which sandbox is making the request.
Can my end-users see the audit log themselves? That’s your call - MIOSA gives you the audit data; how you expose it in your white-labeled UI is up to you. Most white-label customers surface “your activity” to end-users in their own dashboards.
How do I migrate existing in-app secret storage to MIOSA Secrets? Bulk import via SDK is the easiest path:
for user_id, openai_key in your_existing_secret_store.items():
client.secrets.set(
name="openai_key",
value=openai_key,
scope="external_user",
external_user_id=user_id,
expose_as_env="OPENAI_API_KEY",
) Once verified, delete from your old store.