Secure Auth Patterns
Implement secure authentication and authorization using JWT, OAuth2, session management, RBAC, permissions, and resource ownership verification.
MIT-0 · Free to use, modify, and redistribute. No attribution required.
⭐ 0 · 501 · 0 current installs · 0 all-time installs
MIT-0
Security Scan
OpenClaw
Suspicious
high confidencePurpose & Capability
Name and description align with the included content: the SKILL.md and implementation playbook provide JWT, session, OAuth2, RBAC, and refresh-token patterns and code samples consistent with an authentication/authorization guidance skill. There are no unrelated binaries or unrelated capabilities requested.
Instruction Scope
The runtime instructions contain many concrete code samples that reference environment secrets and runtime components (e.g., process.env.JWT_SECRET, JWT_REFRESH_SECRET, SESSION_SECRET, REDIS_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, FRONTEND_URL) but the skill does not declare these as required. The playbook also demonstrates redirecting access tokens in a query parameter (res.redirect(...?token=...)), which is an insecure pattern that can leak tokens via referrers or logs. Overall the instructions stay within auth/authz scope but assume access to secrets and external services without making those requirements explicit.
Install Mechanism
This is an instruction-only skill with no install spec and no code files to execute: lowest install risk. Nothing is downloaded or written to disk by the skill itself.
Credentials
Registry metadata lists no required environment variables or credentials, yet the code samples assume multiple sensitive environment values (JWT_SECRET, JWT_REFRESH_SECRET, SESSION_SECRET, REDIS_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, FRONTEND_URL, etc.). The absence of declared required secrets is an incoherence: if you run or copy these examples into a runtime, you'll need secrets; the skill does not request or document how those should be provided safely.
Persistence & Privilege
The skill does not request persistent presence (always: false), does not modify other skills or global agent config, and does not request elevated platform privileges. Autonomous invocation remains allowed (platform default), but there are no extra persistence flags set by this skill.
What to consider before installing
This playbook appears to be legitimate guidance on implementing auth/authz, but be aware of two things before you use it: (1) the examples assume many secrets and external endpoints (JWT_SECRET, JWT_REFRESH_SECRET, SESSION_SECRET, REDIS_URL, GOOGLE_CLIENT_ID/SECRET, FRONTEND_URL, etc.) even though the skill metadata lists none — you should not paste real secrets into the agent or public prompts and you should plan how to supply these values securely (environment-only, secret manager, etc.); (2) some shown patterns can leak tokens (redirecting access tokens in a URL query string is risky). If you plan to adopt the examples, review and adapt them to your platform's secure secret storage, avoid placing tokens in URLs (prefer HttpOnly secure cookies or one-time server-side exchanges), and validate the code in a controlled dev environment before deploying.Like a lobster shell, security has layers — review code before you run it.
Current versionv1.0.0
Download ziplatest
License
MIT-0
Free to use, modify, and redistribute. No attribution required.
SKILL.md
Authentication & Authorization Patterns
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems.
Description
USE WHEN:
- Implementing user authentication systems
- Securing REST or GraphQL APIs
- Adding OAuth2/social login or SSO
- Designing session management
- Implementing RBAC or permission systems
- Debugging authentication issues
DON'T USE WHEN:
- Only need UI/login page styling
- Task is infrastructure-only without identity concerns
- Cannot change auth policies
Authentication vs Authorization
| AuthN (Authentication) | AuthZ (Authorization) |
|---|---|
| "Who are you?" | "What can you do?" |
| Verify identity | Check permissions |
| Issue credentials | Enforce policies |
| Login/logout | Access control |
Authentication Strategies
| Strategy | Pros | Cons | Best For |
|---|---|---|---|
| Session | Simple, secure | Stateful, scaling | Traditional web apps |
| JWT | Stateless, scalable | Token size, revocation | APIs, microservices |
| OAuth2/OIDC | Delegated, SSO | Complex setup | Social login, enterprise |
JWT Implementation
Generate Tokens
import jwt from 'jsonwebtoken';
function generateTokens(user: User) {
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // Short-lived
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' } // Long-lived
);
return { accessToken, refreshToken };
}
Verify 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 = jwt.verify(token, process.env.JWT_SECRET!);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
Refresh Token Flow
app.post('/api/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
try {
// Verify refresh token
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);
// Check if token exists in database (not revoked)
const storedToken = await db.refreshTokens.findOne({
token: await hash(refreshToken),
expiresAt: { $gt: new Date() }
});
if (!storedToken) {
return res.status(403).json({ error: 'Token revoked' });
}
// Generate new access token
const user = await db.users.findById(payload.userId);
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
);
res.json({ accessToken });
} catch {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
Session-Based Authentication
import session from 'express-session';
import RedisStore from 'connect-redis';
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' });
}
req.session.userId = user.id;
req.session.role = user.role;
res.json({ user: { id: user.id, email: user.email } });
});
// Logout
app.post('/api/auth/logout', (req, res) => {
req.session.destroy(() => {
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
});
OAuth2 / Social Login
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
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) => {
// 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
});
}
return done(null, user);
}));
// 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) => {
const tokens = generateTokens(req.user);
res.redirect(`${FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`);
});
Authorization: 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);
}
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) => {
await db.users.delete(req.params.id);
res.json({ message: 'User deleted' });
}
);
Permission-Based Access
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 requirePermission(...permissions: Permission[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
const hasAll = permissions.every(p =>
rolePermissions[req.user.role]?.includes(p)
);
if (!hasAll) return res.status(403).json({ error: 'Insufficient permissions' });
next();
};
}
Resource Ownership
function requireOwnership(resourceType: 'post' | 'comment') {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
// Admins can access anything
if (req.user.role === Role.ADMIN) return next();
const resource = await db[resourceType].findById(req.params.id);
if (!resource) return res.status(404).json({ error: 'Not found' });
if (resource.userId !== req.user.userId) {
return res.status(403).json({ error: 'Not authorized' });
}
next();
};
}
// Usage: Users can only update their own posts
app.put('/api/posts/:id', authenticate, requireOwnership('post'), updatePost);
Password Security
import bcrypt from 'bcrypt';
import { z } from 'zod';
const passwordSchema = z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[a-z]/, 'Must contain lowercase')
.regex(/[0-9]/, 'Must contain number')
.regex(/[^A-Za-z0-9]/, 'Must contain special character');
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 12); // 12 rounds
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
Best Practices
✅ Do
- Use HTTPS everywhere
- Hash passwords with bcrypt (12+ rounds)
- Use short-lived access tokens (15-30 min)
- Store refresh tokens in database (revocable)
- Validate all input
- Rate limit auth endpoints
- Log security events
- Use secure cookie flags (httpOnly, secure, sameSite)
❌ Don't
- Store passwords in plain text
- Store JWT in localStorage (XSS vulnerable)
- Use weak JWT secrets
- Trust client-side auth checks only
- Expose stack traces in errors
- Skip server-side validation
- Ignore rate limiting
Common Pitfalls
| Issue | Solution |
|---|---|
| JWT in localStorage | Use httpOnly cookies |
| No token expiration | Set short TTL + refresh tokens |
| Weak passwords | Enforce strong policy with zod |
| No rate limiting | Use express-rate-limit + Redis |
| Client-only auth | Always validate server-side |
Files
2 totalSelect a file
Select a file to preview.
Comments
Loading comments…
