# Foundations Capstone

From [Descope Fundamentals](https://learn.descope.io/c/foundations) — Learn the platform by configuring your own project — every module ends in work the app checks for real.

Take this course interactively at https://learn.descope.io/c/foundations/capstone

---

![Build a B2C Login Flow. 23 seconds.](video:capstone-build-a-b2c-login-flow)

## The scenario

- You are building sign-in for a consumer product — a fitness tracker, a recipe app, anything with individual accounts and no tenants
- Users arrive on their phones, so a long registration form loses them
- The product stores a display name and a profile photo URL, and users expect to change both
- One action, deleting an account, is destructive enough that it should ask for proof again

Nothing here needs a second system, a domain, or a paid tier. Everything is done in your own free-tier project, and the Flow Runner is the whole test environment.

## What to build

- One Flow that handles both sign-up and sign-in, passwordless or password-based — your choice, but be able to say why
- A second factor, applied at least on sign-up
- A screen where a signed-in user can read and update their display name
- A step-up prompt in front of account deletion, gated on the `su` claim

Build it as one Flow with branches rather than four separate Flows. The branching is the part worth practising: a real product has one front door, and what varies is what happens after Descope works out whether it has seen this person before.

![One Flow, branching. A welcome screen offers Enchanted Link or a social provider; both converge on a single "Is new user?" condition, which sends first-time users through profile collection and returns everyone else straight to the end.](capstone-build-a-b2c-login-flow)

## What "finished" looks like

- A new user can complete sign-up end to end in the Flow Runner and appears under Users in the Console
- That same address can then sign in again without creating a second user
- The session token issued at the end carries an `amr` claim naming the method you actually used
- Attempting the destructive action without stepping up is refused, and the same action after stepping up succeeds
- Editing the display name changes it in the Console, not just on screen

Check each of these yourself rather than assuming. The Flow Runner is the fastest loop — run the journey, then open Users and Audit in the Console and see whether what you believe happened is what Descope recorded. Two of these criteria are deliberately negative: a login flow that only ever gets tested with correct input is a login flow you know nothing about.

## Where each piece was taught

- The Flow itself — Flows Fundamentals, especially screens, conditions, and testing
- Choosing the method — Authentication, which covers what each family costs the user
- The second factor — MFA and Risk-Based Security
- The `su` claim and its timeout — Sessions and Tokens, Step-Up Sessions
- Reading the token you were issued — Sessions and Tokens, Token Validation

## Where people get stuck

- Building four Flows instead of one with conditions, then finding that a change has to be made four times
- Testing only the happy path, so the "user already exists" branch is discovered by a real user
- Assuming a flow context value is populated in the Flow Runner — it starts with almost nothing in it, so a condition on user attributes can look broken when it is merely untested
- Treating step-up as something checked once at login; the `su` claim expires on its own, so the check belongs in front of the action

## Summary

- That a complete journey combines several modules' concepts at once
- That one branching Flow is the maintainable shape, not four parallel ones
- That the negative cases are what tell you the Flow works

---

![Debug a Broken Implementation. 22 seconds.](video:capstone-debug-a-broken-implementation)

## A method that works on unfamiliar systems

- Reproduce it first, and know exactly which action produces the symptom
- Decide which side is lying — the Console shows configuration, the token shows what was issued, the audit log shows what happened
- Compare the three; a bug is almost always a disagreement between two of them
- Change one thing, re-run, and put it back if it did not help

The temptation with an auth problem is to start changing settings, because there are so many and one of them must be wrong. Resist it. The audit log and the issued token between them explain most failures without a single configuration change, and a project you have half-altered while guessing is a harder problem than the one you started with.

## Break it yourself, then fix it

- Create a role in the Console, assign it to a user, and attach no permissions to it — then watch a permission check fail for a user who visibly has the role
- Build a Flow with a condition on a user attribute and run it in the Flow Runner, where flow context starts nearly empty — the branch never fires, and the Flow looks broken when only the test harness is
- Sign in with a Magic Link and read the `amr` claim on the resulting token, then write a check that requires the string `magiclink` — it will reject every correct sign-in, because Descope reports `email`
- Set a short Step Up Token Timeout, step up, wait it out, and retry the protected action — a check that ran once at login now passes when it should not, and one that runs per action starts failing "for no reason"
- Give an HTTP connector an action whose error handling swallows failures, and watch a downstream step read a value that never arrived

Each of these is a real failure mode, not a puzzle invented for the exercise, and each is quicker to diagnose once you have caused it on purpose. The last one generalises: a step that reports success because nothing threw is the hardest kind of bug to see, and connectors are where it most often hides.

## Reading the evidence

- The audit log answers "did this happen at all" — a missing entry is as informative as a failed one
- A decoded session token answers "what was this user actually issued", including `amr`, `su`, expiry, and any custom claims
- The Flow Runner answers "does the journey work", but only for the context it supplies, which is not production's context
- The Console answers "what did somebody configure", which is the claim the other three are checking

When two sources disagree, believe the token and the audit log over the Console and over your memory of what you configured. The Console shows intent; the other two show outcome.

## Scope note

- Flow logic, sessions, and connectors are all covered in this course, and the exercises above use only those
- SSO and SCIM appear in the curriculum's description of this project but belong to Enterprise Identity, and are not taught here
- A Foundations candidate is not expected to debug a SAML assertion or a SCIM provisioning run

## Summary

- How to approach an unfamiliar broken implementation methodically
- That the token and the audit log outrank the Console as evidence
- That causing a failure deliberately is the fastest way to learn to recognize it

---

![Secure an App End to End. 28 seconds.](video:capstone-secure-an-app-end-to-end)

## The scenario

- A small application with a frontend and a backend you control, in whatever language you are quickest in
- Three kinds of route: open to anyone, open to any signed-in user, and open only to users holding a particular role
- The backend must not trust anything the frontend tells it about who the caller is

The application can be trivial — three endpoints returning three strings is enough. What is being demonstrated is the boundary, not the product. Reuse the roles you created in the Authorization activity rather than inventing new ones.

## What to build

- Client-side sign-in using a Flow, producing a session the frontend holds
- A backend that validates the session token on every protected request, against the project's JWKS
- A role check on one route, reading the claim rather than asking Descope on each call
- Correct rejections: 401 when there is no valid session, 403 when there is a session without the required role

The one design decision worth making consciously is where validation happens. Put it in one place the requests pass through rather than at the top of each handler; the route that forgets to call the check is the route that gets found.

## What "finished" looks like

- An unauthenticated request to a protected route is refused, and the response says 401 rather than redirecting to a login page the API has no use for
- A valid session reaches the protected route and the handler can name the user
- A valid session without the required role is refused with 403, not 401 — the caller is known, just not permitted
- Tampering with one character of the token's payload causes rejection
- A token that has expired is rejected without a call to Descope

Those last two are the criteria that separate validating a token from decoding one. Decoding is trivial and proves nothing; a backend that reads claims without verifying the signature will happily believe a token a user edited in their browser. If both of those cases still reach your handler, the integration is not finished regardless of how well the happy path works.

## Where each piece was taught

- Wiring the SDKs into a real application — SDKs and Application Integration, backend and client
- What the token contains and why a backend can check it alone — Sessions and Tokens, Token Validation
- Roles, permissions, and what the platform will not enforce for you — Authorization, especially Application-Side Enforcement
- Putting claims into the token in the first place — Authorization, Custom Claims and JWT Templates

## Where people get stuck

- Decoding the JWT instead of verifying it, which passes every test written with valid tokens
- Fetching JWKS on every request rather than caching it, turning the auth provider into a hard dependency of every call
- Returning 401 for an authorization failure, which tells a correctly signed-in user to sign in again
- Checking roles in the frontend and calling it done — the frontend check is a courtesy to the user, and the backend check is the security control

## Summary

- That end-to-end security requires work on both sides of the boundary
- That verifying a signature, not reading claims, is what makes a token trustworthy
- That 401 and 403 answer different questions and should not be collapsed

---
