Hello, We are integrating Descope as the identity...
# ask-a-descoper
m
Hello, We are integrating Descope as the identity layer for our platform. In addition to our primary environment, we run a
sandbox
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 Hanikel
a
Hi @Mateusz Mecina Thanks for the detailed! For strong isolation (sandbox vs prod), the most robust and recommended approach is to use separate Descope projects. Each project has its own issuer and signing keys, so tokens are cryptographically isolated, and your production API can safely reject any sandbox-issued token (and vice versa). Your current approach (single project + multiple OAuth clients) can work, but it relies on strict validation on your side — for example enforcing
aud
(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/oidc
s
How do we prevent being charged twice for tenants and users? This is a public sandbox intended to be used for integration testing from our customers. E.g. similar to how stripe sandboxes work
m
@Akash Jivane Thanks, that helps. Just to make sure I understood correctly: if I want to support this in my React app through the Descope SDK, would the recommended setup be to create two separate OIDC / Federated Apps — one for production and one for sandbox? What is still unclear to me is how token claims should be managed in that model, especially when using Federated Apps. I am not fully sure which claims would be available in the issued tokens and where exactly I should define the environment-specific separation. My original idea: • create two separate inbound apps: one for production and one for sandbox • use the customization available in Session Management, especially the customized User JWT and Access Key JWT, to add claims that would distinguish prod vs sandbox So I would like to confirm: • If Federated Apps are the right pattern, where is the correct place to define and control the claims that let my backend reliably distinguish production tokens from sandbox tokens? • Or is it valid to stay with inbound apps and customize the JWTs there for environment-specific claims? My main goal is to make sure the backend can reject a token minted for the wrong environment in a reliable way, while still allowing a smooth switch from main to sandbox for the same signed-in user.
a
Hi @Mateusz Mecina yes, you've got the right idea! Two separate inbound (Federated) apps — one for prod, one for sandbox — is exactly the pattern to follow here. You don't need separate projects for this to work cleanly. On the claims side: the
aud
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.
m
@Akash Jivane thank you. I have some other idea. Would it be possible to skip the inbounds app at all? My idea to simplify my initial approach: • Keep one React app and one normal Descope login integration using project ID. • Represent the active runtime mode with a small custom claim, for example
env: "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.
a
@Mateusz Mecina Yes, this simplified approach is actually valid and honestly cleaner for your use case! You're right about the
aud
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!
m
@Akash Jivane thanks a lot. I think there is another open question. Is there any chance in that scenario (one tenant) to keep in separation accessKeys or ouath clients (created via DCR). Or is it sth we need to accept if we are going to reuse one tenant in those prod/sandbox contexts? 🤔
a
Within a single project and single tenant, access keys and DCR-created OAuth clients don't have native prod/sandbox isolation at the Descope level. They're project-scoped, so there's no built-in way to hard-separate them by environment the way you'd get with two separate projects. That said, you can still manage this logically: • Access keys — you can use naming conventions (e.g.
sandbox_*
) 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!