Mateusz Mecina
04/13/2026, 12:58 PMsandbox environment: a separate application and database that mirrors production for safe testing.
What we want to achieve
1. Strong isolation: A JWT issued for the sandbox environment must not be accepted by our production API (and ideally vice versa if we ever need symmetric rules). Users should not be able to call production backends using a sandbox-oriented token.
2. Smooth UX: After the user signs in to the main environment, we want them to switch to the sandbox context without a full interactive login (e.g. silent OIDC / refresh-style flow), while still ending up with credentials that are clearly scoped to sandbox.
Current approach
We are experimenting with two OAuth applications in Descope (separate client IDs/secrets): one for production and one for sandbox, with a silent authorization request for the sandbox client.
We would like to confirm the recommended Descope pattern for this:
1. Is it valid and supported to keep one Descope project (and one tenant) and achieve isolation via multiple OAuth clients and/or custom claims / audiences?
2. Alternatively, should sandbox be modeled as a separate project or tenant, or another mechanism, to guarantee token separation?
3. What is the best way to ensure tokens are cryptographically or semantically bound to “production” vs “sandbox” so our resource servers can reject cross-environment tokens reliably?
Thank you.
cc: @Steffen HanikelAkash Jivane
04/13/2026, 2:04 PMaud (audience) and/or a custom claim like env. Since tokens are still signed by the same project, the separation is not as strong and depends on correct validation.
For the smooth UX, your idea of doing a silent OIDC flow for the sandbox client is absolutely valid — this lets you reuse the session and issue a sandbox-scoped token without a full login.
I would recommend:
• If isolation/security is critical → use separate projects
• If you prefer simpler setup → single project + enforce aud / custom claims strictly
You can also refer to the OIDC setup from the docs: https://docs.descope.com/sso/oidcSteffen Hanikel
04/13/2026, 2:06 PMMateusz Mecina
04/14/2026, 7:24 AMAkash Jivane
04/14/2026, 8:45 AMaud claim in the token is automatically set to the client ID of the OAuth app that issued it. So right out of the box, your backend can use aud to tell prod tokens apart from sandbox tokens without any extra configuration. That alone might be enough depending on how strict you need the check to be.
If you want something more explicit (like an env: "production" or env: "sandbox" field in the token), you can add that via a JWT Template under Session Management. The way to drive the value dynamically is to set a custom user attribute or use a flow action that stamps the appropriate env value before the token is issued — then surface it in the template.
So to directly answer your questions:
• Inbound apps are the right pattern
• aud gives you environment separation for free
• JWT Templates (under Session Management) are where you'd add explicit env claims — and you'd drive the value from a flow or user attribute.Mateusz Mecina
04/14/2026, 9:20 AMenv: "prod" or env: "sandbox".
• When the user clicks Switch to sandbox, run an authenticated flow for the already signed-in user, using the current refresh JWT, and replace the active session with the returned one.
• Frontend reads that claim and chooses:
◦ api.myapp.com for env=prod
◦ sandbox.api.myapp.com for env=sandbox
• Backend also enforces the same claim and rejects tokens for the wrong environment.
If I understand correctly when I run and authenticated flow I can only append value to aud claim - so in this approach it won't work for me, right? Wondering wether this is a valid approach or not.Akash Jivane
04/14/2026, 9:32 AMaud point — in the inbound app model, aud maps to the client ID, so it's tied to that OAuth layer. But in the approach you're describing (native Descope SDK, no inbound apps), aud isn't the right lever. Instead, you'd drive environment separation entirely through a custom claim, which is exactly what you're proposing.
Here's how I'd wire it up:
1. Add a custom user attribute — something like active_env — defaulting to "prod"
2. In your JWT Template (Session Management → User JWT), surface it as a claim: env: user.custom.active_env
3. When the user clicks Switch to Sandbox, trigger an authenticated Descope flow that updates active_env to "sandbox" and exchanges the current refresh token for a new session token
4. Switching back to prod is just the reverse — same flow, sets it back to "prod"
Your frontend reads env from the token and routes accordingly, and your backend rejects anything where env doesn't match the expected value. Clean and simple.
One thing worth keeping in mind: make sure the flow that updates active_env has some guard on it — you don't want users to be able to freely flip themselves to sandbox in production if that matters for your security model. A simple check in the flow (e.g. only allow sandbox switching if a certain condition is met) should cover it.
But overall — yes, this works, and you don't need inbound apps for it!Mateusz Mecina
04/14/2026, 12:28 PMAkash Jivane
04/14/2026, 12:43 PMsandbox_*) and scope them with specific permissions or allowed tenants. That way it's clear which keys belong to which environment, even if Descope doesn't enforce the split itself.
• DCR OAuth clients — when registering clients dynamically, you can include env metadata in the client registration (e.g. a sandbox field in client metadata). Your authorization server logic would then handle routing/enforcement based on that.
The honest trade-off here: logical separation works fine in practice, but if strict infrastructure-level isolation of access keys and OAuth clients is a hard requirement, separate projects are really the cleaner answer for that piece.
So to summarise where you are: the single-project + custom env claim approach works great for user-facing token isolation. But for access keys and DCR clients, you're either accepting shared infrastructure with logical labeling, or you move those to separate projects. Might be worth deciding which matters more for your threat model!