Authentication Guide
TSDIAPI-JWT-Auth is a plugin for the TSDIAPI-Server
framework that simplifies JWT-based authentication and authorization. It includes utilities for token creation, session validation, and custom guards to secure API endpoints effectively.
Features
- Token Management: Generate and verify JWT tokens with customizable payloads and expiration times.
- Session Protection: Use built-in or custom session validation logic for secure API access.
- Custom Guards: Easily register and reference multiple guards to support various security requirements.
- Environment Integration: Supports configuration through
.env
files to streamline deployment.
🚀 Quick Start
-
Install JWT Authentication Plugin:
npm install @tsdiapi/jwt-auth
Or use the CLI:
tsdiapi plugins add jwt-auth
-
Configure Environment Variables:
JWT_SECRET_KEY=your-secret-key
JWT_EXPIRATION_TIME=604800 # 7 days in seconds
🔐 Authentication Types
1. Bearer Token Authentication (Recommended for JWT)
import createPlugin from "@tsdiapi/jwt-auth";
import { createApp } from "@tsdiapi/server";
createApp({
plugins: [
createPlugin({
secretKey: "your-secret-key", // Use JWT_SECRET_KEY in .env as an alternative
expirationTime: 60 * 60 * 24 * 7, // Token valid for 7 days
guards: {
adminOnly: async (session) => {
if (session.role !== "admin") {
return "Only administrators are allowed!";
}
return true;
}
}
})
]
});
🔒 Protecting Endpoints
Important Rules for Route Definition:
- Every route using guards MUST define a 403 response code with the following schema:
.code(403, Type.Object({
error: Type.String(),
})) - Response codes (
code()
) must be defined immediately after the HTTP method (get, post, etc.) - Authentication (
auth()
) and guards (guard()
) should be defined after response codes - The handler should be defined last
Example of Correct Route Definition
useRoute("feature")
.get("/protected")
.code(200, Type.Object({
data: Type.Any()
}))
.code(403, Type.Object({
error: Type.String()
}))
.auth("bearer")
.guard(JWTGuard({ guardName: 'adminOnly' }))
.handler(async (req) => {
const userId = req.user.id;
return { status: 200, data: { userId } };
})
.build();
Applying the JWTGuard
Secure API endpoints using JWTGuard
. You can use it in two ways:
- For standard bearer token validation:
useRoute()
.get('/protected/endpoint')
.code(200, Type.Object({
message: Type.String(),
}))
.code(403, Type.Object({
error: Type.String(),
}))
.auth('bearer')
.guard(JWTGuard())
.handler(async (req) => {
return {
status: 200,
data: { message: 'Access granted' }
}
})
.build();
- For custom guard validation:
useRoute()
.get('/admin/dashboard')
.code(200, Type.Object({
message: Type.String(),
}))
.code(403, Type.Object({
error: Type.String(),
}))
.auth('bearer')
.guard(JWTGuard({ guardName: 'adminOnly' }))
.handler(async (req) => {
return {
status: 200,
data: { message: 'Welcome to admin dashboard' }
}
})
.build();
Note: Make sure you have registered the guard with the same name (adminOnly
in this example) during plugin initialization when using custom guards.
Registering Custom Guards
You can register custom guards during plugin initialization. These guards can later be referenced by name:
createApp({
plugins: [
createPlugin({
secretKey: "your-secret-key",
guards: {
adminOnly: async (session) => {
if (session.role !== "admin") {
return "Only administrators are allowed!";
}
return true;
},
},
}),
],
});
2. Using JWT Auth Provider
The JWTAuthProvider
is the core service for handling JWT-based authentication:
import { useJWTAuthProvider } from "@tsdiapi/jwt-auth";
// Sign in and generate token
const authProvider = useJWTAuthProvider();
const token = await authProvider.signIn({
userId: "123",
role: "admin"
});
// Verify token
const session = await authProvider.verify<{ userId: string; role: string }>(token);
if (session) {
console.log("Authenticated User:", session.userId);
}
3. Basic Authentication
useRoute("feature")
.get("/basic-protected")
.code(200, Type.Object({
message: Type.String()
}))
.code(403, Type.Object({
error: Type.String()
}))
.auth("basic", async (req) => {
const auth = req.headers.authorization;
if (!auth) {
return {
status: 401,
data: { error: "No credentials provided" }
};
}
const [username, password] = Buffer.from(auth.split(' ')[1], 'base64')
.toString()
.split(':');
const isValid = await validateCredentials(username, password);
if (!isValid) {
return {
status: 401,
data: { error: "Invalid credentials" }
};
}
return true;
})
.build();
4. API Key Authentication
useRoute("feature")
.get("/api-key-protected")
.code(200, Type.Object({
from: Type.String(),
key: Type.String(),
}))
.code(403, Type.Object({
error: Type.String()
}))
.auth("apiKey")
.guard(APIKeyGuard({ guardName: 'reportService' }))
.handler(async (req) => {
return {
status: 200,
data: {
from: 'APIKey session',
key: req.session.apiKey
}
};
})
.build();
🔒 Security Best Practices
-
Token Storage:
- Store JWT_SECRET_KEY securely
- Use environment variables
- Never commit secrets to version control
-
Token Expiration:
- Set reasonable expiration times
- Use refresh tokens for long-lived sessions
- Implement token revocation
-
Password Security:
- Use strong password hashing
- Implement rate limiting
- Require password complexity
-
API Key Security:
- Rotate API keys regularly
- Use key prefixes for identification
- Implement key revocation
-
Custom Guards:
- Implement role-based access control
- Validate session data
- Handle edge cases
API Reference
Plugin Options
Option | Type | Description |
---|---|---|
secretKey | string | Secret key for signing JWT tokens. |
expirationTime | number | Token expiration time in seconds. |
guards | Record<string, ValidateSessionFunction> | Custom guards for validating sessions. |
⚠️ Common Issues
-
Token Expired:
.code(401, Type.Object({
error: Type.String(),
code: Type.String({ const: "TOKEN_EXPIRED" })
})) -
Invalid Token:
.code(401, Type.Object({
error: Type.String(),
code: Type.String({ const: "INVALID_TOKEN" })
})) -
Missing Token:
.code(401, Type.Object({
error: Type.String(),
code: Type.String({ const: "MISSING_TOKEN" })
})) -
Guard Validation Failed:
.code(403, Type.Object({
error: Type.String(),
code: Type.String({ const: "GUARD_VALIDATION_FAILED" })
}))
📚 Additional Resources
License
This plugin is open-source and available under the MIT License.