Auth Patterns that I Worked With
User auth, over and over
Over the years, I have built and shipped authentication for applications and services across different teams and tech stacks. Every pattern below is something I experienced and implemented. Looking back, they all fall into a few common patterns, just with different names each time: the app manages its own login, login is handled by another service, one directory covers many apps at once, a machine proves who it is without a human, or a simple key is used because the risk is low.
1. Sessions and JWTs
This is the simplest pattern. The app manages the user itself.
-
Django model auth. Django’s built-in
Usermodel, plus session middleware. You log in with a username and password, Django checks it against the database, then gives you a session cookie. The cookie only holds a session key — the real data stays on the server, in thedjango_sessiontable. Decoded, a row looks roughly like this:session_key: 8xf3a1c9d4e2b6f0a1c3d5e7f9a1b3c5 session_data: {"_auth_user_id": "42", "_auth_user_backend": "django.contrib.auth.backends.ModelBackend"} expire_date: 2026-10-02 09:00:00The browser only ever holds that first value, the session key. Everything else stays on the server.
-
JWT with Django REST Framework. Same login check, different token. Instead of a server-side session, DRF returns a signed access token and refresh token (usually via
djangorestframework-simplejwt). The server only checks the signature, no session lookup, which is why this fits an SPA or a mobile app calling an API.
2. OAuth and Cognito
At some point, the app stops storing passwords itself. Another service checks the user and handles login instead, like Google, or a managed identity service like Cognito.
sequenceDiagram
participant U as User
participant App as App (Django / SPA)
participant IdP as Google / Cognito
U->>App: Click "Sign in"
App->>IdP: Redirect with authorization request
U->>IdP: Enter credentials, consent
IdP-->>App: Redirect back with auth code
App->>IdP: Exchange code for tokens
IdP-->>App: access token + id token + refresh token
App-->>U: Session established
- Google OAuth. “Sign in with Google” is the flow above, plus OpenID Connect on top. The
id_tokensays who the user is; theaccess_tokenlets you call Google’s APIs on their behalf. - AWS Cognito. Two parts are easy to mix up. User Pool is the actual user directory — sign-up, sign-in, hosted login page — and it runs the code exchange above. Identity Pool is a separate step: it takes a verified identity (from a User Pool, from Google, or from your own backend via Developer Authenticated Identities) and exchanges it for temporary AWS credentials. That’s what lets a mobile app talk to S3 or DynamoDB directly, without a fixed key stored in the app.
amazon-cognito-identity-jsin a React app. A different way to reach the same User Pool, no redirect, no hosted page. The library talks to the User Pool directly from the browser using the SRP protocol, so the real password never travels over the network. Sign-up, sign-in, MFA, and session refresh are all direct API calls. Same tokens come out at the end, just a custom login form instead of a hosted one.
3. Enterprise SSO
Once a company has more than one app, login stops being separate per app. One directory decides who you are, for all of them.
sequenceDiagram
participant U as User
participant SP as Django App (Service Provider)
participant IdP as AWS Identity Center / Entra / Ping
U->>SP: Request protected page
SP-->>U: Redirect to IdP with SAML AuthnRequest
U->>IdP: Authenticate (or reuse existing SSO session)
IdP-->>U: Signed SAML assertion
U->>SP: POST assertion to ACS endpoint
SP-->>U: Validate signature, establish session
- Microsoft Active Directory. The oldest pattern here, using the LDAP protocol. A bind operation checks a username and password directly against the directory.
django-auth-ldapdoes this bind, then maps AD groups to Django permissions. - Microsoft Entra ID. The cloud version of AD. Same idea, but it speaks OIDC and SAML instead of raw LDAP binds, behind an app registration and whatever conditional-access rules IT has added, like MFA or device checks.
- Ping (PingFederate/PingOne). Plays a similar role to Entra, a SAML/OIDC identity provider in front of many apps. Common when a company needs to connect more directories than one identity provider can cover alone.
- AWS Identity Center (SSO). IAM Identity Center acts as the identity provider, using the SAML2 protocol. On one project, the Django app was the SAML side, via
djangosaml2: users click in from the AWS access portal, a signed assertion logs them in, and the app never stores a password for that user.
4. Workload identity
Not every login comes from a human. CI jobs and services need to authenticate too, and the modern answer is a short-lived token from a trusted source, not a fixed secret stored somewhere.
sequenceDiagram
participant GH as GitHub Actions job
participant OIDC as GitHub's OIDC provider
participant STS as AWS STS
GH->>OIDC: Request short-lived ID token
OIDC-->>GH: Signed OIDC token (claims: repo, branch)
GH->>STS: AssumeRoleWithWebIdentity(token)
STS-->>GH: Temporary AWS credentials
- OIDC: GitHub Actions to AWS assume role. AWS trusts GitHub’s OIDC provider directly. An IAM role’s trust policy checks values like repo and branch, and the workflow exchanges a short-lived token, minted by GitHub for that job, for temporary AWS credentials via
AssumeRoleWithWebIdentity. No fixed AWS key ever sits in a GitHub secret. - Service account: client ID and secret. An older, simpler version of the same idea. A non-human client proves itself with a client ID and secret, using OAuth’s client credentials grant, and gets back its own scoped token. No user here, just a machine proving it is who it says it is.
5. Just enough auth
Not everything needs a full identity system behind it.
- API key / bearer token in a header. A shared secret sent in a header, like
Authorization: Bearer …orX-API-Key, checked against a stored value. Good enough for service-to-service calls or third-party integrations where a full identity system would be overkill. - Basic auth on Celery Flower. A simple username and password check in front of an internal monitoring dashboard, no directory, no token, just enough to keep it off the open internet.
Auth for AI agents and MCP
AI agents do not need a new pattern here. They just use the same patterns above, as a new kind of caller. Before, a browser tab or a CI job made the request. Now, an agent makes the request, following the same steps.
MCP’s own auth follows the OAuth flow from section 2, with the agent playing the role a browser tab played before. MCP uses OAuth 2.1 with PKCE: the MCP server is the thing being protected, and the agent is the OAuth client asking for a scoped, time-limited token, not a shared, fixed API key.
sequenceDiagram
participant U as User
participant Agent as AI Agent (MCP client)
participant AuthZ as Org IdP (Entra / Ping / Cognito)
participant MCP as MCP Server (resource)
U->>Agent: "Do X using tool Y"
Agent->>AuthZ: Authorization request (PKCE)
U->>AuthZ: Consent / existing SSO session
AuthZ-->>Agent: Auth code
Agent->>AuthZ: Exchange for scoped, audience-bound token
AuthZ-->>Agent: Short-lived access token (aud = MCP server)
Agent->>MCP: Call tool with token
MCP-->>Agent: Result, only within scope
A few things matter even more once an LLM is involved:
- Enterprise SSO is still useful, it becomes the identity provider behind this OAuth flow. Whatever already sits behind a company’s SAML/OIDC logins, like Entra or Ping from section 3, can sit behind MCP too. Connecting an agent to internal tools is just adding one more app to the same directory, still governed by the same groups and policies.
- Short-lived, scoped tokens (see section 2) are the main protection here. An agent should never hold a raw password or session cookie. A token scoped to one server that expires in minutes limits the damage far more than a long-lived credential would.
- Agent-to-service calls map to the workload-identity pattern from section 4. A federated, short-lived credential beats a service-account secret sitting in an env var, the same reasoning that moved CI off static AWS keys.
- API keys are still fine for simple cases — a function-calling agent hitting a public weather API, say. Treat them like the Flower example in section 5: fine for something low-risk and narrow, never the credential behind anything important.
Summary
Remove the vendor names and there are really just a few patterns: the app manages login itself, another service verifies the user, one directory covers identity across many apps, a machine proves itself without a human, or a simple key stands in because the risk is low. AI agents and MCP don’t add a new one, they just make enterprise SSO and workload identity the default. An agent acting on your behalf needs what those patterns already give: another system to confirm who’s asking, and a token narrow and short-lived enough that a mistake stays small.
~J.Gu [Jerry]