DEV Community

Cover image for How to test Descope auth without real keys
FetchSandbox
FetchSandbox

Posted on

How to test Descope auth without real keys

Title options:

  1. How to test Descope auth without real keys
  2. How to test Descope passwordless auth without a project

Descope is easy to start with, but the part I would test first is not the OTP screen or the magic-link email.

It is what your app does with the session JWT after Descope returns it.

FetchSandbox has a Descope sandbox for the flows most apps wire up first:

  • email OTP signup
  • email magic-link sign-in
  • session refresh
  • agent/access-key exchange

No real Descope project. No real keys. No inbox setup.

The useful part is that the sandbox models the failure cases too: wrong OTP, expired session, replayed magic link, and the big one: accepting a forged session JWT because the app decoded it instead of verifying it.

Here is the concrete flow I would test.

  1. Send a magic link:
POST /v1/auth/magiclink/signin/email
Enter fullscreen mode Exit fullscreen mode
  1. Verify the one-time token:
POST /v1/auth/magiclink/verify
Enter fullscreen mode Exit fullscreen mode

The sandbox returns the shape your app should care about:

{
  "sessionJwt": "...",
  "refreshJwt": "...",
  "user": {
    "userId": "U2..."
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Call your protected route with the sessionJwt.

This is where the bug usually lives.

Bad handlers do something like:

const claims = jwt.decode(sessionJwt);
if (claims.role === "admin") allow();
Enter fullscreen mode Exit fullscreen mode

That accepts whatever claims are inside the token. A forged alg: none token can become role=admin if the handler never checks the signature.

The fixed version verifies the session JWT against Descope's JWKS, or uses Descope's SDK validation path. It should reject alg: none, reject bad signatures, cache JWKS, and refresh on kid mismatch.

The sandbox route for this bug is meant to prove that difference:

  • forged unsigned token -> 401
  • valid signed session token -> 200

That is the whole point. Do not just test "magic link works." Test that the session you trust is actually verified.

You can also test refresh behavior:

POST /v1/auth/refresh
GET /v1/auth/me
Enter fullscreen mode Exit fullscreen mode

If refresh returns 401, send the user back through sign-in. Do not keep rendering authenticated UI from a decoded-but-unverified token.

Docs are here if you want the exact Descope sandbox flows: https://fetchsandbox.com/docs/descope

Top comments (0)