The Faster We Build with AI, the More Dangerous Bad Auth Becomes - And the Rarer Good Auth Becomes
While everyone races to ship with AI, understanding authentication and authorization is quietly becoming the most valuable skill in backend engineering.
Let me set a scene that probably sounds familiar.
You're building a new backend service. You open Cursor, type a prompt - "implement OAuth 2.0 login with JWT tokens" - and in about 8 seconds you have 150 lines of clean, readable, production-looking code. You skim it. It looks right. You move on.
I've done this. Most engineers I know have done this.
And here's what nobody talks about: that code is often almost correct. Not obviously broken - just subtly, silently wrong in ways that don't surface until something goes wrong in production. A token that should expire doesn't. A user who should lose access still has it. An endpoint that should be protected isn't.
The AI didn't fail you. You just didn't know what to check.
That's what this post is about.
First, Let's Get the Basics Right - Because Most People Still Conflate These
Before we go deeper, one thing I need to address: Authentication and Authorization are not the same thing, and confusing them is the root cause of more bugs than I can count.
Authentication answers: Who are you?
It's the process of verifying identity. When you log in with a username and password, or sign in with Google - that's authentication.
Authorization answers: What are you allowed to do?
It's the process of determining permissions. After the system knows who you are, authorization decides what resources you can access and what actions you can take.
Here's a simple way to remember it:
Authentication is the bouncer checking your ID at the door.
Authorization is the VIP list that decides which rooms you can enter.
You need both. In that order. Always.
Most AI-generated auth code handles authentication passably. Authorization is where things quietly go wrong — missing scope checks, over-permissioned tokens, endpoints that assume "logged in" means "allowed."
Keep this distinction in your head for the rest of this post.
Why This Matters More Right Now Than It Ever Has
A few years ago, building a backend with proper auth took meaningful effort. You had to understand the flow, write the code, debug it, test it. That friction was annoying - but it also meant you spent time with the code. You understood it.
Today, that friction is largely gone.
Copilot, ChatGPT, Claude, Cursor - these tools can scaffold an entire authentication system in minutes. That's genuinely incredible. It's also genuinely risky when the engineer shipping that code doesn't understand what's under the hood.
Think about what's happening at scale:
- Thousands of developers are shipping AI-generated auth code every day
- Many of them are newer engineers who haven't been burned by auth bugs yet
- The code looks correct - it has the right function names, the right libraries, the right structure
- But subtle mistakes - wrong grant type, missing token validation, no refresh rotation - slip through
The attack surface of the internet is growing faster than collective security knowledge. That's the uncomfortable reality.
And here's the flip side - the opportunity: engineers who genuinely understand auth, who can look at AI-generated OAuth code and immediately spot what's missing or wrong, are increasingly rare and increasingly valuable.
Let's make sure you're one of them.
OAuth 2.0: What It Actually Is (And What People Get Wrong About It)
OAuth 2.0 is an authorization framework - not an authentication protocol. This trips people up constantly. OAuth 2.0 on its own does not tell you who the user is. It tells you what a client application is allowed to do on behalf of a user.
Authentication on top of OAuth 2.0 is what OpenID Connect (OIDC) adds. If you're using OAuth to log users in, you're almost certainly using OIDC on top — whether you know it or not.
OAuth 2.0 works through flows, also called grant types. Different situations call for different flows. This is where I see AI-generated code go wrong most often - it picks a flow, but not necessarily the right one for the context.
The Four Flows You Need to Know
1. Authorization Code Flow + PKCE
Use this for: web apps, mobile apps, SPAs - anything where a user is present
This is the most common and most secure flow for user-facing applications. Here's how it works:
- Your app redirects the user to the authorization server (e.g., Google, Auth0)
- The user logs in and grants permission
- The authorization server redirects back to your app with a short-lived authorization code
- Your app exchanges that code for an access token (and optionally a refresh token)
The code is single-use and expires quickly. Even if someone intercepts the redirect, they can't do much with just the code.
PKCE (Proof Key for Code Exchange - pronounced "pixie") is the security enhancement that makes this flow safe for public clients like SPAs and mobile apps that can't securely store a client secret.
How PKCE works:
- Your app generates a random code verifier (a long random string)
- It hashes that verifier to create a code challenge
- It sends the code challenge with the authorization request
- When exchanging the code for tokens, it sends the original code verifier
- The authorization server verifies the verifier matches the challenge
This means even if an attacker intercepts the authorization code, they can't exchange it - they don't have the code verifier.
AI-generated code mistake I see here: Omitting PKCE entirely for SPAs, or implementing the Authorization Code flow without PKCE for mobile apps. It looks fine. It isn't.
2. Client Credentials Flow
Use this for: machine-to-machine (M2M), service-to-service, backend APIs
No user involved here. This is for when one service needs to talk to another service. Your backend requests a token directly from the authorization server using its client ID and client secret.
Backend Service → Authorization Server: "Here's my client ID and secret"
Authorization Server → Backend Service: "Here's your access token"
Backend Service → Resource API: "Here's my access token"
Simple. But the mistakes here are costly:
- Storing the client secret in version control (it happens more than you think — and AI-generated setup instructions sometimes put secrets in example config files that get committed)
- Not rotating secrets - a secret that never changes is a liability
- Over-scoping the token - requesting all scopes "just in case" instead of the minimum required
- Not handling token expiry - the service breaks at 2am because nobody implemented token refresh logic
3. Implicit Flow
Status: Deprecated. Do not use.
I'm mentioning this because AI tools sometimes still generate it - especially if trained on older code. The Implicit Flow was designed for SPAs before PKCE existed. It returned access tokens directly in the URL fragment, which is a significant security issue. PKCE-enhanced Authorization Code Flow replaced it entirely.
If you see Implicit Flow in AI-generated code, replace it.
4. Resource Owner Password Credentials (ROPC)
Status: Highly discouraged. Almost never appropriate.
This flow has the user send their username and password directly to your application, which then exchanges them for tokens. It defeats the entire point of OAuth - the user should never give their credentials to a third-party app.
The only time this might be acceptable is for highly trusted first-party applications, and even then, there are usually better alternatives. AI tools sometimes generate this for "simplicity." It isn't worth it.
Token Security: Where Most Bugs Live
Let's talk about the actual tokens - because this is where subtle mistakes cause the most damage.
JWT: The Good, The Bad, and The Dangerous
JSON Web Tokens (JWTs) are everywhere. An access token in most OAuth implementations is a JWT. Here's what one looks like under the hood:
header.payload.signature
-
Header: algorithm used to sign the token (
RS256,HS256, etc.) - Payload: the claims - user ID, scopes, expiry, issuer
- Signature: cryptographic proof the token hasn't been tampered with
The most dangerous JWT mistake: accepting alg: none
Early JWT libraries had a flaw - if the header said "alg": "none", some libraries would skip signature verification entirely. An attacker could forge any token they wanted.
The fix: always explicitly specify which algorithms your server accepts. Reject anything else.
import jwt
# Bad - trusting the token's own algorithm header
# Some libraries will skip verification if alg is "none"
payload = jwt.decode(token, secret)
# Better - explicitly specify accepted algorithms
payload = jwt.decode(
token,
secret,
algorithms=["RS256"] # reject anything else
)
Other JWT mistakes I've observed:
-
No expiry (
expclaim missing): A token that never expires is a permanent backdoor. Always set short expiry - 15 minutes for access tokens is common. - Sensitive data in the payload: The payload is base64-encoded, not encrypted. Anyone can decode it. Don't put passwords, PII, or sensitive business logic in JWT claims.
-
Not validating the issuer (
iss) and audience (aud): A token issued for Service A should not be accepted by Service B. Always validate these claims. -
Weak signing secrets: If you're using HMAC (HS256), your secret needs to be long and random.
secret123is not a secret.
Refresh Tokens: The Part Everyone Forgets to Secure
Access tokens are short-lived. Refresh tokens are long-lived - they're used to get new access tokens without the user re-authenticating. That makes them valuable targets.
Refresh Token Rotation is the security practice you should be using:
Every time a refresh token is used, the authorization server issues a new refresh token and invalidates the old one. If an attacker steals a refresh token and tries to use it after the legitimate user already has, the server detects the reuse and can invalidate the entire session.
AI-generated code often implements refresh token logic without rotation. It works - it just doesn't protect against token theft.
Refresh token storage matters too:
- In web apps:
HttpOnly,Secure,SameSite=Strictcookies - not localStorage (XSS can read localStorage) - In mobile apps: the device's secure keychain/keystore
- In backend services: encrypted at rest
Token Revocation: The Feature Nobody Implements Until They Need It
What happens when a user reports their account was compromised? Or when an employee leaves the company? Or when you detect suspicious activity?
You need to revoke tokens - immediately invalidate them before they expire naturally.
JWTs are stateless by design, which means the server doesn't track them. A revoked JWT is still cryptographically valid until it expires. There are two practical approaches:
Token blocklist: Maintain a fast store (Redis works well) of revoked token IDs (
jticlaim). Check every incoming token against this list. Overhead is low if done right.Short expiry + refresh rotation: Keep access tokens extremely short-lived (5-15 minutes). Revoke the refresh token at the authorization server. The user's access naturally expires within minutes.
Most AI-generated code implements neither. It's not a bug that shows up in testing — only in an incident.
Common Vulnerabilities: A Quick-Reference List
These are the ones I check for when reviewing auth code — AI-generated or otherwise:
Scope Creep: Requesting more OAuth scopes than needed. Always use minimum required scopes. If your app only needs to read a user's email, don't request write access to their entire account.
Open Redirect: OAuth flows use redirect URIs. If your server doesn't strictly validate the redirect URI against a whitelist, an attacker can redirect tokens to their own server. Always validate exact URI matches — not just domain matches.
State Parameter Missing: The state parameter in OAuth flows is CSRF protection. It's a random value your app sends with the authorization request and expects back in the callback. Without it, an attacker can trick a user into completing an OAuth flow the attacker initiated. AI-generated code frequently omits this.
Logging Tokens: Access tokens and refresh tokens should never appear in logs. One misconfigured logger that records request headers or bodies, and your tokens are in your logging system - accessible to anyone with log access. Scrub tokens from logs explicitly.
Trusting Client-Provided User IDs: If your API accepts a user_id in the request body and acts on it without verifying it matches the authenticated token's subject claim - that's a broken authorization vulnerability. Always derive the user identity from the validated token, never from user-provided input.
What This Means for Your Career
Here's what I've been observing and thinking about.
AI tools are genuinely compressing the time it takes to build things. That's not going away. More backends will be built, by more people, faster than ever before.
But auth is not a problem that's been solved by AI generation. It's a domain where:
- Mistakes are invisible until they're exploited
- "Looks correct" and "is correct" are dangerously different things
- The consequences of getting it wrong fall on real users
Engineers who can look at AI-generated auth code and immediately know what to look for, why certain patterns are dangerous, and what questions to ask - those engineers are not being replaced by AI tools. They're the ones who can actually use those tools responsibly.
That's a real differentiator. Not just technically, but professionally.
The Practical Checklist: What to Review in Every Auth Implementation
Whether the code is AI-generated or hand-written, here's what I run through:
- [ ] Are we using the right OAuth flow for this use case?
- [ ] Is PKCE implemented for public clients?
- [ ] Are JWTs validated with explicit algorithm specification?
- [ ] Do access tokens have short expiry (
expclaim set)? - [ ] Is the
iss(issuer) andaud(audience) being validated? - [ ] Is refresh token rotation implemented?
- [ ] Are refresh tokens stored securely (not in localStorage)?
- [ ] Is there a token revocation strategy?
- [ ] Are redirect URIs strictly validated against a whitelist?
- [ ] Is the
stateparameter used to prevent CSRF? - [ ] Are tokens being excluded from logs?
- [ ] Does each OAuth scope request follow the principle of least privilege?
- [ ] Is user identity derived from the token, not from request input?
Print it. Bookmark it. Run through it before every auth PR you review.
Final Thought
AI didn't make auth easier to get right. It made it easier to get almost right - which in security, is the most dangerous place to be.
The engineers who will stand out in the next few years aren't the ones who can prompt the fastest. They're the ones who understand what the prompt produced deeply enough to know when to trust it, when to question it, and when to rewrite it.
Auth is one of those areas where that judgment is everything.
Go build - but build like you know what's under the hood.
Found this useful? A backend engineer - always learning, always observing, and writing about what I pick up along the way. If this resonated,
let's connect on LinkedIn | GitHub.
#Authentication #Authorization #OAuth #Security #BackendDevelopment #SoftwareEngineering
Top comments (1)
Great stuff!!