Software Development with AI 13 min read Updated Jul 16, 2026

How to Plan an OAuth or SSO Integration With AI

SSO arrives as a checkbox and lands as a lifecycle. Plan the integration before you build it: the identity key, the linking rule that isn't account takeover, what a provider's groups may and may not grant, and what happens when somebody leaves.

Get the OAuth & SSO Integration Prompt

The deal needs SSO

That's the whole requirement as it reaches you: a line on a security questionnaire, a renewal date, and a customer who assumes it's a toggle. And in one narrow sense they're right — you can have a working "Sign in with" button by Thursday. The button is genuinely small.

What comes attached to it isn't. You are agreeing to accept a second answer to the question of who your users are: a directory you don't control, run by a team you've never met, at a company whose joiner-and-leaver process silently becomes part of yours. Every question that used to have one answer now has two — where the account comes from, what happens when the email changes, what happens when somebody quits, who decides they're an admin — and none of those questions arrive with the ticket. They arrive four months later, one at a time, usually as a surprise.

The integration is a week. The lifecycle is the project

Almost everything that goes wrong with SSO goes wrong after it works. The flow is well-trodden and the libraries are good; you will get a token, verify it, and create a session, and that part is a known quantity. The failures live in the seams the protocol doesn't cover: which field you decided was the user's identity, what you did when a provider identity showed up wearing an existing customer's email address, what your app does with a group claim it doesn't recognise, and what happens to somebody's live session at 4pm on their last day.

So this is a planning document, and it's worth being precise about what it is not. It's not the decision about whether to add SSO — that's a mechanism choice with its own trade-offs, and it happens upstream of this. It's not a permission model: who may archive a project is a question about authorization, and SSO doesn't change the answer, it only changes how the asker got in the door. And it's not a security review, because there is nothing to review yet.

A model is genuinely useful here and its limits are worth naming. Give it your provider, your client type, your existing user table, your lifecycle rules and your constraints, and it will draft the plan, push on the fields you left vague, and reliably raise the linking question — which is the one most teams discover in production. What it cannot do is know your provider. NewPrompt's part is smaller still: it builds the prompt, in your browser. It never reaches your identity provider, performs no login, tests no callback and no token, discovers nothing about your tenant or configuration, reads no secret or key or metadata document, links no account and provisions nobody, and validates none of the security properties below. Every provider-specific detail in a draft — an endpoint, a claim name, a config flag — is a guess unless you checked it against their documentation. What comes back is a candidate plan for a qualified engineer to review, and it is not evidence that the integration is correct, secure, or going to work.

Step 1: Pick the identity key before anything else

One decision governs the rest of the document, and the obvious answer is the wrong one. Key on the provider's stable subject identifier, scoped to the issuer — the `(iss, sub)` pair. Store both, look users up by both, and treat everything else as profile.

Email is not an identity key. It is mutable profile data that happens to look unique. People change it; corporate addresses get reassigned to an entirely different human when someone leaves; some providers will assert an address the user simply typed into a form; and `email_verified` may be absent, false, or meaningless for the domain in question — and its absence is not evidence of anything. Key on email and you have built a system where changing a string in a directory you don't control silently repoints an account.

Say the same thing about what you're accepting as proof. An access token is a bearer credential for an API: it says the holder may call something, not who is standing there, and it isn't bound to your application at all — a token minted for a different client can be handed to an app that naively calls a userinfo endpoint and calls the result a login. What proves identity is the ID token, whose audience is your client, whose nonce is yours, and whose signature, issuer and expiry you check. If a draft ever suggests obtaining an access token and treating the result as a login, that is the single most common mistake in this domain, and it has a name: token substitution.

Step 2: Decide what happens to the users you already have

If your product already has accounts — and it does, or you wouldn't be adding SSO to it — this is the section that matters most and the one that is usually a footnote. A provider identity arrives asserting an email address that matches an existing local user. What do you do?

The tempting answer is to link them and sign them in. That is account takeover with a friendly interface: if any provider you accept will assert an address it hasn't verified, an attacker registers that address there, signs in, and lands inside your customer's existing account with all their data, no password involved. Auto-linking on an email match is defensible only when the provider asserts verification, and you trust that provider to actually verify that domain, and the local account's address is verified too. That is three conditions, and most teams check zero.

The safe pattern costs one screen: don't link at login. Tell the user an account already exists, make them authenticate the way they already can, and confirm the link from a session that's already theirs. Better still, do linking only from account settings, where the person is by definition already authenticated. Then design unlinking, because an account whose only credential was the provider becomes an account nobody can get into. And plan for the mixed-method user — the person who has both — because they will be the one who files the confusing ticket.

Step 3: Choose the flow from the client you actually have

Authorization code with PKCE, for essentially everything. The interesting question isn't which flow, it's which client you are, because that determines what you can protect.

A confidential client can keep a secret: a server-rendered app whose backend holds the credential, a daemon, a backend-for-frontend. A public client cannot — a single-page app ships its source to the browser, a mobile binary can be unpacked, and a secret in either is not a secret, it's a string. That distinction is about whether the credential can genuinely be kept, not about the platform's name, and getting it wrong produces a false sense of client authentication rather than an error message.

Three controls, three different properties, and a plan that assumes one covers another will be wrong about which. `state` protects the callback from CSRF — without it an attacker can walk a victim's browser through a flow with the attacker's own code and quietly log them into the attacker's account. `nonce` binds the ID token to the request you actually made. PKCE makes an intercepted authorization code unredeemable. Write them as properties you require rather than settings you tick: your framework probably does all three, and "probably" is what this document exists to convert into a sentence somebody can check.

One boundary here. The plan says which token proves identity and what you verify on it — signature, issuer, audience, expiry, nonce. It stops there. What can go wrong inside the token itself, how it's stored, and the revocation problem that stateless tokens create are a review of an implementation that doesn't exist yet, and that's a different job with different traps.

Step 4: Read their claims. Don't obey them

Every SSO plan eventually reaches the sentence "and we'll map their groups to our roles," and it is doing far more work than it looks like.

A provider's groups belong to a directory: a different team's organisational structure, on a different lifecycle, carrying no application meaning. Map them straight through and anybody who can create or rename a group over there grants privilege in your product over here, with no review on your side and no visibility. Group membership is routinely broad, inherited, and stale. Names collide and get renamed, and your authorization drifts without a single deploy. And you lose the ability to answer "who can do this?" from your own system, which is the one question you will be asked in an audit.

So keep your own role model as the authority and treat claims as inputs to an explicit mapping you maintain. Unmapped groups grant nothing — default deny, always. And consider not claim-granting your privileged roles at all in the first phase: let admin be a local grant while you watch what their groups actually contain for a quarter. It costs a config screen and buys you the difference between an integration and a delegation.

Step 5: Plan the leaver, not just the joiner

Provisioning is the part everybody plans, because it's the part that fails loudly on day one. Deprovisioning is the part that fails silently for two years.

Just-in-time provisioning creates the account on first login from the claims. It's cheap, it needs no integration, and it means nobody is filing tickets to create users. It also gives you no list of who *should* have access, hands the entire account to whatever your claim mapping did on that first login, and — the part that matters — **has no offboarding story whatsoever**. Directory-driven provisioning costs you an integration and gives you the leaver path and the access review. The honest framing is that JIT solves onboarding for free and solves offboarding not at all, and most teams need both.

Then the question almost no plan writes down: the provider disables the user. What happens to the session they already have? Nothing. Your session is your artifact, with your TTL, and disabling someone at the directory prevents future authentications — it does not reach into a session that already exists. So a terminated employee keeps working for as long as your session lives, and the worst part is that the person who disabled them saw a success message and believes it's done, so nobody is looking. Closing that gap costs something and somebody has to choose: shorter sessions with revalidation, back-channel logout, directory-driven deprovisioning, or accepting it in writing with the number named. All four are legitimate. Not deciding is the only option that isn't.

Logout has the same shape at a smaller scale. Local logout ends your session and touches nothing at the provider, so the user logs out, clicks the SSO button, and is instantly back in — which looks broken and is both systems working correctly. RP-initiated logout fixes it — your app redirects the user to the provider end-session endpoint so both sessions die — and it depends on them completing a redirect they may abandon. Back-channel logout fixes it properly, provider to server, and needs an endpoint from you and support from them.

Step 6: Write down the operational half, and who owns it

The provider is now a hard dependency on your login page, and dependencies have days like everyone else. When their directory is down, your customer's whole company cannot get in, and you have no lever at all. Do you leave password login enabled as a second door? That's a real trade-off — availability against attack surface — and it belongs to whoever owns the risk, not to whoever noticed it. What you must not do is discover the answer during the outage.

Then the unglamorous list that decides whether this survives its first year. Their signing keys rotate: fetch and cache the discovery metadata rather than pinning a key, because a hard-coded key is a scheduled outage with a date you haven't been told. Redirect URIs are exact-match and are places tokens get sent, so somebody owns that list and every addition is reviewed. Staging and production are separate clients with separate secrets, and client secrets themselves expire — usually on a date nobody wrote down. Every one of those has an owner or it has nobody.

Cut over in a way you can reverse. Dark first against one internal tenant with the real provider. Then the customer's users, with both methods live and linking offered rather than forced. Then — when the customer says so, not when you're ready — password login goes off for that tenant only, behind a per-tenant flag, because reversing a config change beats reversing a deploy at 9am. Everything left over goes in an open-questions list with a name against it, and the ones that belong to the customer's IT team should be sent before the sprint starts, not after.

Common mistakes

The first four are the same mistake at different depths: trusting an assertion further than it goes.

  • Treating an access token as a login. It's a credential for an API, it isn't bound to your app, and a token issued for another client will happily produce a userinfo response. The ID token, with your audience and your nonce, is the thing that says who logged in.
  • Calling OAuth an authentication protocol. It's delegated authorization — it has no way to tell your client who authenticated, or whether anyone did. That's precisely why OIDC exists, and a plan that blurs the two will make the mistake above somewhere.
  • Auto-linking on an email match. The tidiest possible onboarding and a full account takeover if any accepted provider will assert an unverified address. Three conditions make it safe; most plans check none of them.
  • Trusting group claims as permissions. You have handed role assignment to a directory admin at another company, with no review and no visibility, and you can no longer answer who can do what from your own system.
  • Forgetting the person with both. Someone links SSO, keeps their password, and now has two ways in and one of them is not covered by anything the directory does — they are the ticket nobody can reproduce, and every plan that treats users as either-or has already created them.
  • Assuming a disabled user is a logged-out user. Disabling at the provider stops future logins. It does nothing to a live session, and the person who clicked disable believes otherwise, which is what makes it dangerous rather than merely wrong.
  • Letting a draft invent the provider. Endpoint names, claim names, config flags and defaults are provider-specific and a model will produce confident, plausible, wrong ones — NewPrompt reaches no identity provider, and neither does the model. Check every one against their documentation.

Adding enterprise SSO to a product that already has users

A project-management product with 4,100 password users takes on one enterprise customer with 820 seats and an OIDC directory. Brownfield is the ordinary case and the hard one: greenfield SSO has no existing accounts to link, no mixed-method users, and no awkward cutover. Everything below is fictional.

One customer's directory, planned end to end before a client is registered: an identity key chosen against the tempting one, a linking rule that costs a screen and prevents a takeover, a group mapping that grants nothing by default, three questions belonging to somebody else's IT team, and a leaver gap nobody asked about that is the only actively dangerous line on the page
SSO INTEGRATION PLAN — enterprise OIDC for Kestrel (project management)
                                            plan v1, nothing built, no client yet

WHERE WE ARE
  4,100 existing users, email + password, our own user table, our own sessions.
  One customer (820 seats) needs SSO to renew. Their directory speaks OIDC.
  The decision to support SSO is already made and is not this document's.

WHAT THIS DOCUMENT IS NOT
  Not the mechanism choice — that was settled elsewhere.
  Not a review of our existing login code.
  Not the permission model. Who may archive a project is unchanged by this.

──────────────────────────────────────────────────────────────
IDENTITY KEY
  Key on (iss, sub). Store both. Never key on email.
      ^ this is the decision everything below inherits, and the tempting
        alternative is the wrong one. Email is mutable profile data: people
        change it, corporate addresses get reassigned to a different human,
        and some providers assert an address the user merely typed. sub is
        stable and meaningless, which is exactly what an identity key should be.
  We still store email. As a display attribute, refreshed on each login,
  never as the thing we look them up by.

PROVIDER ASSUMPTIONS (stated, because they are assumptions)
  A-1  Their IdP issues an ID token and asserts email_verified.   UNVERIFIED.
       Q1 depends on this and Q1 is the blocking question.
  A-2  Their directory is the source of truth for who is an employee.
       Believed. Not our system of record.
  A-3  They will tell us when someone leaves.  <- see LEAVER. This is the
       assumption most SSO plans make silently and never write down.

FLOW
  Authorization code + PKCE. Our app is a server-rendered web app with a
  backend, so it is a confidential client and can hold a secret — and PKCE
  goes on anyway, per current guidance, because it costs nothing and closes
  code interception regardless of client type.
  state    CSRF on the callback. Without it an attacker can complete a flow
           with their own code and silently log a victim into the attacker's
           account.
  nonce    binds the ID token to our request. Different property, not optional
           because we have PKCE.
  Verify on the ID token: signature, iss, aud == our client_id, exp, nonce.
      ^ that verification list is the protocol's contract and stops here. What
        else can go wrong inside a token — algorithm handling, storage, the
        revocation problem stateless tokens create — is a review of an
        implementation that does not exist yet, and it is a different job.

──────────────────────────────────────────────────────────────
LINKING — the 4,100 people who already have accounts
  Rule     Phase 1: never link at login.
  Instead  First SSO login for an unknown (iss, sub) whose asserted email
           matches an existing local account: do NOT sign them in. Show:
           'An account already exists for this address. Sign in with your
           password once to connect your company login.' They authenticate
           locally, then confirm the link from an authenticated session.
  Why      Auto-linking on an email match is account takeover with extra
           steps. If any provider we accept will assert an address it has not
           verified, an attacker signs in and lands inside the victim's
           existing account, no password involved.
      ^ the cheaper version — auto-link when email_verified is true — needs all
        three conditions from the guide, and we can answer none of them yet:
        does their IdP assert it, do we trust them to verify that domain, and
        is the local account's address verified on our side. Q1 gates that
        optimization. It does not gate the rule above, which ships either way.
        The safe default is not waiting on anybody.
  Unlink   Cannot leave an account with no way in. If SSO is the only
           credential, unlinking requires setting a password first.

FIRST LOGIN — unknown (iss, sub), no matching account
  JIT-provision. Create the user, membership in the customer's workspace,
  role = member. Nothing else.
  JIT gets us onboarding for free and gets us nothing for offboarding. That
  is not a detail; see LEAVER.

CLAIMS -> ROLES
  We read their groups claim. We do not obey it.
    their group              our role     mapped by
    kestrel-admins           admin        Dana, in our config, reviewable
    kestrel-users            member       Dana
    anything else            member       default. Unmapped never grants.
      ^ direct mapping would mean anyone who can create a group in their
        directory can grant admin in our product, without our review, on a
        lifecycle we do not see. Their groups are an input we translate. Our
        role model stays the authority, and 'who can archive a project?' still
        has an answer in our own system.
  Admin is NOT claim-granted in phase 1. Local grant only. Revisit after 90
  days of watching what their groups actually contain.

──────────────────────────────────────────────────────────────
THE LEAVER — the part the deal did not mention
  Their IdP disables the user. What happens to a live Kestrel session?
  Today: nothing. Our session is our artifact with our TTL. They see a
  success message and believe access is revoked. It is not — the person keeps
  working for as long as our session lives, which is 30 days with remember-me.
      ^ this is the finding. Nobody asked for it, it is not in the contract,
        and it is the only thing on this page that is actively dangerous.
        Disabling at the IdP is a statement about future logins. It says
        nothing about the session somebody already has.
  Options, none free — Dana and their IT decide together:
    a) short session TTL for SSO users + re-check at the IdP on renewal
    b) back-channel logout: they push a logout token, we kill the session
       (needs an endpoint from us and support from them — Q2)
    c) SCIM: they push deprovisioning, we disable locally. The real answer,
       and the one that costs an integration.
    d) accept it, in writing, with the TTL named.
  Phase 1 takes (a) with a 12-hour TTL, and remember-me is off for SSO-
  authenticated sessions — otherwise 12 hours is a setting the 30-day path
  walks straight past. (c) is the follow-up and it needs a date on it, or (a)
  quietly becomes the permanent answer.

LOGOUT
  Local logout kills our session. It does not touch theirs. So the user logs
  out, clicks Sign in with SSO, and is straight back in with no prompt — which
  looks broken and is correct behavior for both systems.
  Phase 1: RP-initiated logout — redirect to their end_session_endpoint so
  both die. Depends on the user completing the redirect, which they may not.
  We say this in the UI rather than pretending it is airtight.

PROVIDER OUTAGE
  Their IdP is down. 820 people cannot log in and we have no lever.
  Phase 1: password login stays enabled for linked users. It is a real
  security trade-off — a second door — and it is Dana's call, not ours.
  Their security team may require it closed, which converts their outage into
  our outage. Write it down either way. Q3.

OPERATIONS
  Keys      Their signing keys rotate. We fetch JWKS at their discovery URL
            and cache it; a hard-coded key is a scheduled outage. Cache TTL
            TBD — Ana.
  Redirects Exact-match redirect URIs. staging and prod are different clients
            with different secrets. Ana owns the list; every addition is a
            change somebody reviews, because a redirect URI is a place tokens
            can be sent.
  Secrets   Their client secret lives where our other secrets live. It has an
            expiry. Nobody has ever put a date on ours.

CUTOVER
  1. Ship dark. One internal tenant, real IdP, no customer.
  2. The 820: SSO available, password still works, linking prompted not forced.
  3. When the customer says so — not when we say so — password login is
     disabled for their tenant only. Other 3,280 users untouched.
     This step IS Q3's answer, and it deletes the outage fallback above: after
     it runs, their IdP being down is a total outage for all 820. So step 3
     does not run until Q3 is closed in writing, by them, knowing that.
  Fallback: step 3 is a per-tenant flag. Reversing it is a config change, not
  a deploy. That is the whole reason it is a flag.

OPEN — none of these are ours to close
  Q1  Does their IdP assert email_verified, do we trust it for their domain,
      and is the local account's address verified on our side? All three, or
      the expensive path stays. Gates the optimization, not the rule.
      Owner: Dana + their IT.
  Q2  Will they support back-channel logout? Decides whether the leaver gap
      closes properly or gets accepted. Owner: their IT.
  Q3  Password login during an IdP outage: convenience or a door we close?
      Owner: Dana with their security team.

VERIFY LATER, NOT HERE
  Every claim above is a design assertion. Nobody has logged in. The plan says
  verify iss, aud, exp and nonce; whether the code does is a review, against
  code that does not exist, by a security engineer, later. This document is
  the thing that review has something to review against. Fictional company,
  fictional customer, fictional numbers.

Where this fits in NewPrompt

The OAuth & SSO Integration Prompt is the closest starting point and a good one: a copy-paste prompt that takes your providers, client type and existing auth state and works six sections — the flow for your client, CSRF and replay (state, PKCE, nonce), token handling, account linking, scopes, and the provider edge cases that break logins. Two places it and this guide differ, and both are worth knowing before you paste it. Its PKCE rule is scoped to public clients; the position here is that PKCE goes on confidential clients too, which is where current guidance has landed. And its linking rule — link only on a verified email match — is condition one of the three in Step 2, treated as sufficient. This guide does not treat it as sufficient: a verified match is where the argument starts, and never-link-at-login is where it ends up. Beyond that it simply does not reach the lifecycle half — provisioning, the leaver, session versus directory logout, key rotation, outage, cutover — which is Steps 5 and 6, and most of what the integration costs you after it works.

A caution about the route, because it matters more than usual here. The resource names Multi-Step Prompt Builder as its generator, and that tool does something real — it takes a goal and decomposes it into an ordered chain of prompts, each feeding the next. It does not build this prompt. Its own header is explicit that it produces workflows and never a single prompt. The recipe wired to this resource keeps your OAuth goal and sets the workflow type to product, so what comes back is that goal run through ten product-launch phases: a problem statement, a discovery plan with an interview guide, a competitive landscape, requirements, an MVP scope, a risk register, a roadmap, success metrics, a launch, a post-launch review. Coherent for launching a product. Applied to an integration it asks you to interview users about your OAuth flow, and none of the six sections above survive the trip. So take the prompt from the resource page itself, where it's written out in full, and treat the builder as the different tool it is.

Two neighbours mark the edges. Upstream, the Authentication Strategy Prompt is where the mechanism gets decided — sessions versus tokens, passwords versus passwordless, whether to support SSO at all and for whom. That question is settled before this document opens; this one starts from a yes. Downstream, the JWT Security Review Prompt reviews token handling in code that exists: algorithm handling, verification, expiry, payload, storage, revocation, and whether an authorization decision is being made on a claim the client could influence. This plan says which token proves identity and what gets verified on it. What the implementation actually does with it is that review's job, and it needs code to look at.

One level up, the AI Auth & Identity Workflow runs the arc: pick the approach, model the roles and permissions, review the design for gaps, document the identity model. Read what it actually contains: every step after the first is dominated by authorization, and federation appears only as one option inside that first mechanism choice. No step in it plans an integration. That is the hole this one sits in. NewPrompt supplies the prompts and the order; you run them, and every decision above stays with the people whose names are on it.

The reason to write this down before the client is registered is that SSO's failures are not yours to see. A group nobody mapped, a link nobody should have made, a session still working for somebody who left in March — from your side each of those is a healthy user doing normal things, because the system that knows they are wrong is a directory at another company, and it is not the system serving the request. Nobody over there is watching your product either. So the failure sits between two organisations, visible to neither, until somebody in an audit asks a question that has an answer. That question gets asked eventually. This is the document that decides whether the answer is a shrug.

Tools for this guide

Each generates the prompt described above — you run it in your own AI assistant.

Ready-made resources

Reusable prompts and templates for the exact steps in this guide.

Take it further

When this task is one step inside a larger workflow or build.

FAQ

Our provider only speaks SAML. Does any of this still apply?

The lifecycle half applies unchanged, and it's the half that costs you. Identity key, linking rule, claim mapping, provisioning, the leaver, logout, outage, cutover — none of that is protocol-specific; SAML just spells the pieces differently. The signed assertion plays the ID token's role and you validate its audience, recipient, conditions and InResponseTo instead of aud and nonce; the persistent NameID is your subject identifier and the same warning applies about keying on email. What changes is the mechanics — XML assertions over browser POST rather than a code exchange, metadata rather than discovery, single logout rather than RP-initiated. What doesn't change is that a directory you don't control is now answering the question of who your users are.

The customer says their IdP handles everything. Do we still need our own roles?

Yes, and the request is usually well-meant. What they mean is that their directory is the source of truth for who works there — true, and worth relying on. What it can't be is the source of truth for what someone may do inside your product, because your product's permissions are your model, they change on your release schedule, and the person who'd be granting them is a directory admin at another company with no view of what the group names mean on your side. The workable answer usually satisfies everyone: their groups drive membership and coarse roles through a mapping you own and can show them, and anything privileged stays a local grant. That's still SSO. It just isn't delegation.

Can we start with just-in-time provisioning and add the directory integration later?

Almost everybody does, and it's a reasonable first phase as long as "later" has a date and an owner. What you're accepting in the meantime is that you have no leaver path — the account exists until somebody notices it, and nobody's job is noticing. Two things make the interim honest: shorten sessions for provider-authenticated users so the exposure window is hours rather than a remembered month, and put the gap in writing with the number in it, so it's an accepted risk rather than an assumption. The failure mode isn't choosing JIT. It's choosing JIT, calling it phase one, and discovering in year two that phase two never had a name attached.

How much of this can the model actually get right?

The structure, most of the reasoning, and reliably the question you didn't think to ask — the linking one, usually. What it cannot get right is anything specific to your provider: endpoint paths, claim names, which flags that tenant has set, what their admin will and won't agree to, whether their groups mean what their names suggest. It will produce all of those confidently, because a plausible endpoint looks exactly like a real one. The split that works is that the model drafts the shape and the questions, and every provider-specific line gets checked against their documentation by a person, before anybody builds against it.