chore(skills): bootstrap repo-local skills under .agents/skills
This commit is contained in:
343
.agents/skills/api-authentication/SKILL.md
Normal file
343
.agents/skills/api-authentication/SKILL.md
Normal file
@@ -0,0 +1,343 @@
|
||||
---
|
||||
name: api-authentication
|
||||
description: Implement secure API authentication with JWT, OAuth 2.0, API keys, and session management. Use when securing APIs, managing tokens, or implementing user authentication flows.
|
||||
---
|
||||
|
||||
# API Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Implement comprehensive authentication strategies for APIs including JWT tokens, OAuth 2.0, API keys, and session management with proper security practices.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Securing API endpoints
|
||||
- Implementing user login/logout flows
|
||||
- Managing access tokens and refresh tokens
|
||||
- Integrating OAuth 2.0 providers
|
||||
- Protecting sensitive data
|
||||
- Implementing API key authentication
|
||||
|
||||
## Instructions
|
||||
|
||||
### 1. **JWT Authentication**
|
||||
|
||||
```javascript
|
||||
// Node.js JWT Implementation
|
||||
const express = require('express');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
const app = express();
|
||||
const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key';
|
||||
const REFRESH_SECRET = process.env.REFRESH_SECRET || 'your-refresh-secret';
|
||||
|
||||
// User login endpoint
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user in database
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = jwt.sign(
|
||||
{ userId: user.id, email: user.email, role: user.role },
|
||||
SECRET_KEY,
|
||||
{ expiresIn: '15m' }
|
||||
);
|
||||
|
||||
const refreshToken = jwt.sign(
|
||||
{ userId: user.id },
|
||||
REFRESH_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Store refresh token in database
|
||||
await RefreshToken.create({ token: refreshToken, userId: user.id });
|
||||
|
||||
res.json({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn: 900,
|
||||
user: { id: user.id, email: user.email, role: user.role }
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Authentication failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh token endpoint
|
||||
app.post('/api/auth/refresh', (req, res) => {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(401).json({ error: 'Refresh token required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
|
||||
|
||||
// Verify token exists in database
|
||||
const storedToken = await RefreshToken.findOne({
|
||||
token: refreshToken,
|
||||
userId: decoded.userId
|
||||
});
|
||||
|
||||
if (!storedToken) {
|
||||
return res.status(401).json({ error: 'Invalid refresh token' });
|
||||
}
|
||||
|
||||
// Generate new access token
|
||||
const newAccessToken = jwt.sign(
|
||||
{ userId: decoded.userId },
|
||||
SECRET_KEY,
|
||||
{ expiresIn: '15m' }
|
||||
);
|
||||
|
||||
res.json({ accessToken: newAccessToken, expiresIn: 900 });
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: 'Invalid refresh token' });
|
||||
}
|
||||
});
|
||||
|
||||
// Middleware to verify JWT
|
||||
const verifyToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1]; // Bearer token
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Access token required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, SECRET_KEY);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
|
||||
}
|
||||
res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
|
||||
// Protected endpoint
|
||||
app.get('/api/profile', verifyToken, (req, res) => {
|
||||
res.json({ user: req.user });
|
||||
});
|
||||
|
||||
// Logout endpoint
|
||||
app.post('/api/auth/logout', verifyToken, async (req, res) => {
|
||||
try {
|
||||
await RefreshToken.deleteOne({ userId: req.user.userId });
|
||||
res.json({ message: 'Logged out successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Logout failed' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **OAuth 2.0 Implementation**
|
||||
|
||||
```javascript
|
||||
const passport = require('passport');
|
||||
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
||||
|
||||
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 {
|
||||
let user = await User.findOne({ googleId: profile.id });
|
||||
|
||||
if (!user) {
|
||||
user = await User.create({
|
||||
googleId: profile.id,
|
||||
email: profile.emails[0].value,
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName
|
||||
});
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
} catch (error) {
|
||||
return done(error);
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// OAuth routes
|
||||
app.get('/api/auth/google',
|
||||
passport.authenticate('google', { scope: ['profile', 'email'] })
|
||||
);
|
||||
|
||||
app.get('/api/auth/google/callback',
|
||||
passport.authenticate('google', { failureRedirect: '/login' }),
|
||||
(req, res) => {
|
||||
const token = jwt.sign(
|
||||
{ userId: req.user.id, email: req.user.email },
|
||||
SECRET_KEY,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
res.redirect(`/dashboard?token=${token}`);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### 3. **API Key Authentication**
|
||||
|
||||
```javascript
|
||||
// API Key middleware
|
||||
const verifyApiKey = (req, res, next) => {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(401).json({ error: 'API key required' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify API key format and existence
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
const apiKeyRecord = await ApiKey.findOne({ key_hash: keyHash, active: true });
|
||||
|
||||
if (!apiKeyRecord) {
|
||||
return res.status(401).json({ error: 'Invalid API key' });
|
||||
}
|
||||
|
||||
req.apiKey = apiKeyRecord;
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Authentication failed' });
|
||||
}
|
||||
};
|
||||
|
||||
// Generate API key endpoint
|
||||
app.post('/api/apikeys/generate', verifyToken, async (req, res) => {
|
||||
try {
|
||||
const apiKey = crypto.randomBytes(32).toString('hex');
|
||||
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
|
||||
|
||||
const record = await ApiKey.create({
|
||||
userId: req.user.userId,
|
||||
key_hash: keyHash,
|
||||
name: req.body.name,
|
||||
active: true
|
||||
});
|
||||
|
||||
res.json({ apiKey, message: 'Save this key securely' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to generate API key' });
|
||||
}
|
||||
});
|
||||
|
||||
// Protected endpoint with API key
|
||||
app.get('/api/data', verifyApiKey, (req, res) => {
|
||||
res.json({ data: 'sensitive data for API key holder' });
|
||||
});
|
||||
```
|
||||
|
||||
### 4. **Python Authentication Implementation**
|
||||
|
||||
```python
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from functools import wraps
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['JWT_SECRET_KEY'] = 'secret-key'
|
||||
jwt = JWTManager(app)
|
||||
|
||||
@app.route('/api/auth/login', methods=['POST'])
|
||||
def login():
|
||||
data = request.get_json()
|
||||
user = User.query.filter_by(email=data['email']).first()
|
||||
|
||||
if not user or not check_password_hash(user.password, data['password']):
|
||||
return jsonify({'error': 'Invalid credentials'}), 401
|
||||
|
||||
access_token = create_access_token(
|
||||
identity=user.id,
|
||||
additional_claims={'email': user.email, 'role': user.role}
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'accessToken': access_token,
|
||||
'user': {'id': user.id, 'email': user.email}
|
||||
}), 200
|
||||
|
||||
@app.route('/api/protected', methods=['GET'])
|
||||
@jwt_required()
|
||||
def protected():
|
||||
from flask_jwt_extended import get_jwt_identity
|
||||
user_id = get_jwt_identity()
|
||||
return jsonify({'userId': user_id}), 200
|
||||
|
||||
def require_role(role):
|
||||
def decorator(fn):
|
||||
@wraps(fn)
|
||||
@jwt_required()
|
||||
def wrapper(*args, **kwargs):
|
||||
from flask_jwt_extended import get_jwt
|
||||
claims = get_jwt()
|
||||
if claims.get('role') != role:
|
||||
return jsonify({'error': 'Forbidden'}), 403
|
||||
return fn(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@app.route('/api/admin', methods=['GET'])
|
||||
@require_role('admin')
|
||||
def admin_endpoint():
|
||||
return jsonify({'message': 'Admin data'}), 200
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ DO
|
||||
- Use HTTPS for all authentication
|
||||
- Store tokens securely (HttpOnly cookies)
|
||||
- Implement token refresh mechanism
|
||||
- Set appropriate token expiration times
|
||||
- Hash and salt passwords
|
||||
- Use strong secret keys
|
||||
- Validate tokens on every request
|
||||
- Implement rate limiting on auth endpoints
|
||||
- Log authentication attempts
|
||||
- Rotate secrets regularly
|
||||
|
||||
### ❌ DON'T
|
||||
- Store passwords in plain text
|
||||
- Send tokens in URL parameters
|
||||
- Use weak secret keys
|
||||
- Store sensitive data in JWT payload
|
||||
- Ignore token expiration
|
||||
- Disable HTTPS in production
|
||||
- Log sensitive tokens
|
||||
- Reuse API keys across services
|
||||
- Store credentials in code
|
||||
|
||||
## Security Headers
|
||||
|
||||
```javascript
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
next();
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user