auth-implementation-patterns

安装量: 300
排名: #6925

安装

npx skills add https://github.com/wshobson/agents --skill auth-implementation-patterns
Authentication & Authorization Implementation Patterns
Build secure, scalable authentication and authorization systems using industry-standard patterns and modern best practices.
When to Use This Skill
Implementing user authentication systems
Securing REST or GraphQL APIs
Adding OAuth2/social login
Implementing role-based access control (RBAC)
Designing session management
Migrating authentication systems
Debugging auth issues
Implementing SSO or multi-tenancy
Core Concepts
1. Authentication vs Authorization
Authentication (AuthN)
Who are you?
Verifying identity (username/password, OAuth, biometrics)
Issuing credentials (sessions, tokens)
Managing login/logout
Authorization (AuthZ)
What can you do?
Permission checking
Role-based access control (RBAC)
Resource ownership validation
Policy enforcement
2. Authentication Strategies
Session-Based:
Server stores session state
Session ID in cookie
Traditional, simple, stateful
Token-Based (JWT):
Stateless, self-contained
Scales horizontally
Can store claims
OAuth2/OpenID Connect:
Delegate authentication
Social login (Google, GitHub)
Enterprise SSO
JWT Authentication
Pattern 1: JWT Implementation
// JWT structure: header.payload.signature
import
jwt
from
"jsonwebtoken"
;
import
{
Request
,
Response
,
NextFunction
}
from
"express"
;
interface
JWTPayload
{
userId
:
string
;
email
:
string
;
role
:
string
;
iat
:
number
;
exp
:
number
;
}
// Generate JWT
function
generateTokens
(
userId
:
string
,
email
:
string
,
role
:
string
)
{
const
accessToken
=
jwt
.
sign
(
{
userId
,
email
,
role
}
,
process
.
env
.
JWT_SECRET
!
,
{
expiresIn
:
"15m"
}
,
// Short-lived
)
;
const
refreshToken
=
jwt
.
sign
(
{
userId
}
,
process
.
env
.
JWT_REFRESH_SECRET
!
,
{
expiresIn
:
"7d"
}
,
// Long-lived
)
;
return
{
accessToken
,
refreshToken
}
;
}
// Verify JWT
function
verifyToken
(
token
:
string
)
:
JWTPayload
{
try
{
return
jwt
.
verify
(
token
,
process
.
env
.
JWT_SECRET
!
)
as
JWTPayload
;
}
catch
(
error
)
{
if
(
error
instanceof
jwt
.
TokenExpiredError
)
{
throw
new
Error
(
"Token expired"
)
;
}
if
(
error
instanceof
jwt
.
JsonWebTokenError
)
{
throw
new
Error
(
"Invalid token"
)
;
}
throw
error
;
}
}
// Middleware
function
authenticate
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
{
const
authHeader
=
req
.
headers
.
authorization
;
if
(
!
authHeader
?.
startsWith
(
"Bearer "
)
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"No token provided"
}
)
;
}
const
token
=
authHeader
.
substring
(
7
)
;
try
{
const
payload
=
verifyToken
(
token
)
;
req
.
user
=
payload
;
// Attach user to request
next
(
)
;
}
catch
(
error
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Invalid token"
}
)
;
}
}
// Usage
app
.
get
(
"/api/profile"
,
authenticate
,
(
req
,
res
)
=>
{
res
.
json
(
{
user
:
req
.
user
}
)
;
}
)
;
Pattern 2: Refresh Token Flow
interface
StoredRefreshToken
{
token
:
string
;
userId
:
string
;
expiresAt
:
Date
;
createdAt
:
Date
;
}
class
RefreshTokenService
{
// Store refresh token in database
async
storeRefreshToken
(
userId
:
string
,
refreshToken
:
string
)
{
const
expiresAt
=
new
Date
(
Date
.
now
(
)
+
7
*
24
*
60
*
60
*
1000
)
;
await
db
.
refreshTokens
.
create
(
{
token
:
await
hash
(
refreshToken
)
,
// Hash before storing
userId
,
expiresAt
,
}
)
;
}
// Refresh access token
async
refreshAccessToken
(
refreshToken
:
string
)
{
// Verify refresh token
let
payload
;
try
{
payload
=
jwt
.
verify
(
refreshToken
,
process
.
env
.
JWT_REFRESH_SECRET
!
)
as
{
userId
:
string
;
}
;
}
catch
{
throw
new
Error
(
"Invalid refresh token"
)
;
}
// Check if token exists in database
const
storedToken
=
await
db
.
refreshTokens
.
findOne
(
{
where
:
{
token
:
await
hash
(
refreshToken
)
,
userId
:
payload
.
userId
,
expiresAt
:
{
$gt
:
new
Date
(
)
}
,
}
,
}
)
;
if
(
!
storedToken
)
{
throw
new
Error
(
"Refresh token not found or expired"
)
;
}
// Get user
const
user
=
await
db
.
users
.
findById
(
payload
.
userId
)
;
if
(
!
user
)
{
throw
new
Error
(
"User not found"
)
;
}
// Generate new access token
const
accessToken
=
jwt
.
sign
(
{
userId
:
user
.
id
,
email
:
user
.
email
,
role
:
user
.
role
}
,
process
.
env
.
JWT_SECRET
!
,
{
expiresIn
:
"15m"
}
,
)
;
return
{
accessToken
}
;
}
// Revoke refresh token (logout)
async
revokeRefreshToken
(
refreshToken
:
string
)
{
await
db
.
refreshTokens
.
deleteOne
(
{
token
:
await
hash
(
refreshToken
)
,
}
)
;
}
// Revoke all user tokens (logout all devices)
async
revokeAllUserTokens
(
userId
:
string
)
{
await
db
.
refreshTokens
.
deleteMany
(
{
userId
}
)
;
}
}
// API endpoints
app
.
post
(
"/api/auth/refresh"
,
async
(
req
,
res
)
=>
{
const
{
refreshToken
}
=
req
.
body
;
try
{
const
{
accessToken
}
=
await
refreshTokenService
.
refreshAccessToken
(
refreshToken
)
;
res
.
json
(
{
accessToken
}
)
;
}
catch
(
error
)
{
res
.
status
(
401
)
.
json
(
{
error
:
"Invalid refresh token"
}
)
;
}
}
)
;
app
.
post
(
"/api/auth/logout"
,
authenticate
,
async
(
req
,
res
)
=>
{
const
{
refreshToken
}
=
req
.
body
;
await
refreshTokenService
.
revokeRefreshToken
(
refreshToken
)
;
res
.
json
(
{
message
:
"Logged out successfully"
}
)
;
}
)
;
Session-Based Authentication
Pattern 1: Express Session
import
session
from
"express-session"
;
import
RedisStore
from
"connect-redis"
;
import
{
createClient
}
from
"redis"
;
// Setup Redis for session storage
const
redisClient
=
createClient
(
{
url
:
process
.
env
.
REDIS_URL
,
}
)
;
await
redisClient
.
connect
(
)
;
app
.
use
(
session
(
{
store
:
new
RedisStore
(
{
client
:
redisClient
}
)
,
secret
:
process
.
env
.
SESSION_SECRET
!
,
resave
:
false
,
saveUninitialized
:
false
,
cookie
:
{
secure
:
process
.
env
.
NODE_ENV
===
"production"
,
// HTTPS only
httpOnly
:
true
,
// No JavaScript access
maxAge
:
24
*
60
*
60
*
1000
,
// 24 hours
sameSite
:
"strict"
,
// CSRF protection
}
,
}
)
,
)
;
// Login
app
.
post
(
"/api/auth/login"
,
async
(
req
,
res
)
=>
{
const
{
email
,
password
}
=
req
.
body
;
const
user
=
await
db
.
users
.
findOne
(
{
email
}
)
;
if
(
!
user
||
!
(
await
verifyPassword
(
password
,
user
.
passwordHash
)
)
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Invalid credentials"
}
)
;
}
// Store user in session
req
.
session
.
userId
=
user
.
id
;
req
.
session
.
role
=
user
.
role
;
res
.
json
(
{
user
:
{
id
:
user
.
id
,
email
:
user
.
email
,
role
:
user
.
role
}
}
)
;
}
)
;
// Session middleware
function
requireAuth
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
{
if
(
!
req
.
session
.
userId
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Not authenticated"
}
)
;
}
next
(
)
;
}
// Protected route
app
.
get
(
"/api/profile"
,
requireAuth
,
async
(
req
,
res
)
=>
{
const
user
=
await
db
.
users
.
findById
(
req
.
session
.
userId
)
;
res
.
json
(
{
user
}
)
;
}
)
;
// Logout
app
.
post
(
"/api/auth/logout"
,
(
req
,
res
)
=>
{
req
.
session
.
destroy
(
(
err
)
=>
{
if
(
err
)
{
return
res
.
status
(
500
)
.
json
(
{
error
:
"Logout failed"
}
)
;
}
res
.
clearCookie
(
"connect.sid"
)
;
res
.
json
(
{
message
:
"Logged out successfully"
}
)
;
}
)
;
}
)
;
OAuth2 / Social Login
Pattern 1: OAuth2 with Passport.js
import
passport
from
"passport"
;
import
{
Strategy
as
GoogleStrategy
}
from
"passport-google-oauth20"
;
import
{
Strategy
as
GitHubStrategy
}
from
"passport-github2"
;
// Google OAuth
passport
.
use
(
new
GoogleStrategy
(
{
clientID
:
process
.
env
.
GOOGLE_CLIENT_ID
!
,
clientSecret
:
process
.
env
.
GOOGLE_CLIENT_SECRET
!
,
callbackURL
:
"/api/auth/google/callback"
,
}
,
async
(
accessToken
,
refreshToken
,
profile
,
done
)
=>
{
try
{
// Find or create user
let
user
=
await
db
.
users
.
findOne
(
{
googleId
:
profile
.
id
,
}
)
;
if
(
!
user
)
{
user
=
await
db
.
users
.
create
(
{
googleId
:
profile
.
id
,
email
:
profile
.
emails
?.
[
0
]
?.
value
,
name
:
profile
.
displayName
,
avatar
:
profile
.
photos
?.
[
0
]
?.
value
,
}
)
;
}
return
done
(
null
,
user
)
;
}
catch
(
error
)
{
return
done
(
error
,
undefined
)
;
}
}
,
)
,
)
;
// Routes
app
.
get
(
"/api/auth/google"
,
passport
.
authenticate
(
"google"
,
{
scope
:
[
"profile"
,
"email"
]
,
}
)
,
)
;
app
.
get
(
"/api/auth/google/callback"
,
passport
.
authenticate
(
"google"
,
{
session
:
false
}
)
,
(
req
,
res
)
=>
{
// Generate JWT
const
tokens
=
generateTokens
(
req
.
user
.
id
,
req
.
user
.
email
,
req
.
user
.
role
)
;
// Redirect to frontend with token
res
.
redirect
(
`
${
process
.
env
.
FRONTEND_URL
}
/auth/callback?token=
${
tokens
.
accessToken
}
`
,
)
;
}
,
)
;
Authorization Patterns
Pattern 1: Role-Based Access Control (RBAC)
enum
Role
{
USER
=
"user"
,
MODERATOR
=
"moderator"
,
ADMIN
=
"admin"
,
}
const
roleHierarchy
:
Record
<
Role
,
Role
[
]
>
=
{
[
Role
.
ADMIN
]
:
[
Role
.
ADMIN
,
Role
.
MODERATOR
,
Role
.
USER
]
,
[
Role
.
MODERATOR
]
:
[
Role
.
MODERATOR
,
Role
.
USER
]
,
[
Role
.
USER
]
:
[
Role
.
USER
]
,
}
;
function
hasRole
(
userRole
:
Role
,
requiredRole
:
Role
)
:
boolean
{
return
roleHierarchy
[
userRole
]
.
includes
(
requiredRole
)
;
}
// Middleware
function
requireRole
(
...
roles
:
Role
[
]
)
{
return
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
=>
{
if
(
!
req
.
user
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Not authenticated"
}
)
;
}
if
(
!
roles
.
some
(
(
role
)
=>
hasRole
(
req
.
user
.
role
,
role
)
)
)
{
return
res
.
status
(
403
)
.
json
(
{
error
:
"Insufficient permissions"
}
)
;
}
next
(
)
;
}
;
}
// Usage
app
.
delete
(
"/api/users/:id"
,
authenticate
,
requireRole
(
Role
.
ADMIN
)
,
async
(
req
,
res
)
=>
{
// Only admins can delete users
await
db
.
users
.
delete
(
req
.
params
.
id
)
;
res
.
json
(
{
message
:
"User deleted"
}
)
;
}
,
)
;
Pattern 2: Permission-Based Access Control
enum
Permission
{
READ_USERS
=
"read:users"
,
WRITE_USERS
=
"write:users"
,
DELETE_USERS
=
"delete:users"
,
READ_POSTS
=
"read:posts"
,
WRITE_POSTS
=
"write:posts"
,
}
const
rolePermissions
:
Record
<
Role
,
Permission
[
]
>
=
{
[
Role
.
USER
]
:
[
Permission
.
READ_POSTS
,
Permission
.
WRITE_POSTS
]
,
[
Role
.
MODERATOR
]
:
[
Permission
.
READ_POSTS
,
Permission
.
WRITE_POSTS
,
Permission
.
READ_USERS
,
]
,
[
Role
.
ADMIN
]
:
Object
.
values
(
Permission
)
,
}
;
function
hasPermission
(
userRole
:
Role
,
permission
:
Permission
)
:
boolean
{
return
rolePermissions
[
userRole
]
?.
includes
(
permission
)
??
false
;
}
function
requirePermission
(
...
permissions
:
Permission
[
]
)
{
return
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
=>
{
if
(
!
req
.
user
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Not authenticated"
}
)
;
}
const
hasAllPermissions
=
permissions
.
every
(
(
permission
)
=>
hasPermission
(
req
.
user
.
role
,
permission
)
,
)
;
if
(
!
hasAllPermissions
)
{
return
res
.
status
(
403
)
.
json
(
{
error
:
"Insufficient permissions"
}
)
;
}
next
(
)
;
}
;
}
// Usage
app
.
get
(
"/api/users"
,
authenticate
,
requirePermission
(
Permission
.
READ_USERS
)
,
async
(
req
,
res
)
=>
{
const
users
=
await
db
.
users
.
findAll
(
)
;
res
.
json
(
{
users
}
)
;
}
,
)
;
Pattern 3: Resource Ownership
// Check if user owns resource
async
function
requireOwnership
(
resourceType
:
"post"
|
"comment"
,
resourceIdParam
:
string
=
"id"
,
)
{
return
async
(
req
:
Request
,
res
:
Response
,
next
:
NextFunction
)
=>
{
if
(
!
req
.
user
)
{
return
res
.
status
(
401
)
.
json
(
{
error
:
"Not authenticated"
}
)
;
}
const
resourceId
=
req
.
params
[
resourceIdParam
]
;
// Admins can access anything
if
(
req
.
user
.
role
===
Role
.
ADMIN
)
{
return
next
(
)
;
}
// Check ownership
let
resource
;
if
(
resourceType
===
"post"
)
{
resource
=
await
db
.
posts
.
findById
(
resourceId
)
;
}
else
if
(
resourceType
===
"comment"
)
{
resource
=
await
db
.
comments
.
findById
(
resourceId
)
;
}
if
(
!
resource
)
{
return
res
.
status
(
404
)
.
json
(
{
error
:
"Resource not found"
}
)
;
}
if
(
resource
.
userId
!==
req
.
user
.
userId
)
{
return
res
.
status
(
403
)
.
json
(
{
error
:
"Not authorized"
}
)
;
}
next
(
)
;
}
;
}
// Usage
app
.
put
(
"/api/posts/:id"
,
authenticate
,
requireOwnership
(
"post"
)
,
async
(
req
,
res
)
=>
{
// User can only update their own posts
const
post
=
await
db
.
posts
.
update
(
req
.
params
.
id
,
req
.
body
)
;
res
.
json
(
{
post
}
)
;
}
,
)
;
Security Best Practices
Pattern 1: Password Security
import
bcrypt
from
"bcrypt"
;
import
{
z
}
from
"zod"
;
// Password validation schema
const
passwordSchema
=
z
.
string
(
)
.
min
(
12
,
"Password must be at least 12 characters"
)
.
regex
(
/
[
A
-
Z
]
/
,
"Password must contain uppercase letter"
)
.
regex
(
/
[
a
-
z
]
/
,
"Password must contain lowercase letter"
)
.
regex
(
/
[
0
-
9
]
/
,
"Password must contain number"
)
.
regex
(
/
[
^
A
-
Z
a
-
z
0
-
9
]
/
,
"Password must contain special character"
)
;
// Hash password
async
function
hashPassword
(
password
:
string
)
:
Promise
<
string
>
{
const
saltRounds
=
12
;
// 2^12 iterations
return
bcrypt
.
hash
(
password
,
saltRounds
)
;
}
// Verify password
async
function
verifyPassword
(
password
:
string
,
hash
:
string
,
)
:
Promise
<
boolean
>
{
return
bcrypt
.
compare
(
password
,
hash
)
;
}
// Registration with password validation
app
.
post
(
"/api/auth/register"
,
async
(
req
,
res
)
=>
{
try
{
const
{
email
,
password
}
=
req
.
body
;
// Validate password
passwordSchema
.
parse
(
password
)
;
// Check if user exists
const
existingUser
=
await
db
.
users
.
findOne
(
{
email
}
)
;
if
(
existingUser
)
{
return
res
.
status
(
400
)
.
json
(
{
error
:
"Email already registered"
}
)
;
}
// Hash password
const
passwordHash
=
await
hashPassword
(
password
)
;
// Create user
const
user
=
await
db
.
users
.
create
(
{
email
,
passwordHash
,
}
)
;
// Generate tokens
const
tokens
=
generateTokens
(
user
.
id
,
user
.
email
,
user
.
role
)
;
res
.
status
(
201
)
.
json
(
{
user
:
{
id
:
user
.
id
,
email
:
user
.
email
}
,
...
tokens
,
}
)
;
}
catch
(
error
)
{
if
(
error
instanceof
z
.
ZodError
)
{
return
res
.
status
(
400
)
.
json
(
{
error
:
error
.
errors
[
0
]
.
message
}
)
;
}
res
.
status
(
500
)
.
json
(
{
error
:
"Registration failed"
}
)
;
}
}
)
;
Pattern 2: Rate Limiting
import
rateLimit
from
"express-rate-limit"
;
import
RedisStore
from
"rate-limit-redis"
;
// Login rate limiter
const
loginLimiter
=
rateLimit
(
{
store
:
new
RedisStore
(
{
client
:
redisClient
}
)
,
windowMs
:
15
*
60
*
1000
,
// 15 minutes
max
:
5
,
// 5 attempts
message
:
"Too many login attempts, please try again later"
,
standardHeaders
:
true
,
legacyHeaders
:
false
,
}
)
;
// API rate limiter
const
apiLimiter
=
rateLimit
(
{
windowMs
:
60
*
1000
,
// 1 minute
max
:
100
,
// 100 requests per minute
standardHeaders
:
true
,
}
)
;
// Apply to routes
app
.
post
(
"/api/auth/login"
,
loginLimiter
,
async
(
req
,
res
)
=>
{
// Login logic
}
)
;
app
.
use
(
"/api/"
,
apiLimiter
)
;
Best Practices
Never Store Plain Passwords
Always hash with bcrypt/argon2
Use HTTPS
Encrypt data in transit
Short-Lived Access Tokens
15-30 minutes max
Secure Cookies
httpOnly, secure, sameSite flags
Validate All Input
Email format, password strength
Rate Limit Auth Endpoints
Prevent brute force attacks
Implement CSRF Protection
For session-based auth
Rotate Secrets Regularly
JWT secrets, session secrets
Log Security Events
Login attempts, failed auth
Use MFA When Possible
Extra security layer
Common Pitfalls
Weak Passwords
Enforce strong password policies
JWT in localStorage
Vulnerable to XSS, use httpOnly cookies
No Token Expiration
Tokens should expire
Client-Side Auth Checks Only
Always validate server-side
Insecure Password Reset
Use secure tokens with expiration
No Rate Limiting
Vulnerable to brute force
Trusting Client Data
Always validate on server
返回排行榜