@tekcify/auth-backend
v2.0.4
Published
Backend authentication helpers for Tekcify Auth. Provides middleware, guards, and utilities for validating JWT tokens and protecting API routes in NestJS and Express applications.
Readme
@tekcify/auth-backend
Backend authentication helpers for Tekcify Auth. Provides middleware, guards, and utilities for validating JWT tokens and protecting API routes in NestJS and Express applications.
Installation
npm install @tekcify/auth-backend
# or
pnpm add @tekcify/auth-backend
# or
yarn add @tekcify/auth-backendFeatures
- ✅ NestJS Support - Guards and decorators for NestJS applications
- ✅ Express Support - Middleware for Express applications
- ✅ Token Verification - JWT token validation with HMAC/RS256
- ✅ User Info Fetching - Helper to get user information from auth server
- ✅ TypeScript Support - Full type definitions included
Quick Start
Prerequisites
You need the JWT access secret from your Tekcify Auth server. This should match the JWT_ACCESS_SECRET environment variable used by the auth server.
JWT_ACCESS_SECRET=your-jwt-access-secret-hereNote: The auth server URL is centralized in @tekcify/auth-core-client package as AUTH_SERVER_URL (default: http://localhost:7001, override with AUTH_SERVER_URL env). Functions that communicate with the auth server use this constant automatically.
Auth Server Endpoints Used
- Base prefix:
/api - User profile:
GET/PUT /api/user/profile,POST /api/user/profile/picture - Applications:
GET/POST /api/applications,GET /api/applications/public/:clientId,PUT/DELETE /api/applications/:clientId,POST /api/applications/:clientId/logo
NestJS Integration
Step 1: Install Dependencies
pnpm add @tekcify/auth-backend @nestjs/common @nestjs/coreStep 2: Create and Configure the Guard
Create a guard provider in your module:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from '@tekcify/auth-backend/nestjs';
@Module({
providers: [
{
provide: APP_GUARD,
useFactory: () => {
return new JwtAuthGuard({
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
getUserInfo: async (userId: string) => {
// Optional: Fetch user info from your database
// This is called only if getUserInfo is provided
const user = await userRepository.findById(userId);
return user ? { email: user.email } : null;
},
});
},
},
],
})
export class AppModule {}Step 3: Use the Guard in Controllers
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser } from '@tekcify/auth-backend/nestjs';
import type { UserPayload } from '@tekcify/auth-backend/nestjs';
@Controller('api')
@UseGuards(JwtAuthGuard) // Protect entire controller
export class ApiController {
@Get('profile')
getProfile(@CurrentUser() user: UserPayload) {
return {
userId: user.userId,
email: user.email,
scopes: user.scopes,
};
}
@Get('public')
// This route is still protected by the controller-level guard
getPublic() {
return { message: 'Public endpoint' };
}
}Step 4: Per-Route Guard Usage
You can also use the guard on individual routes:
import { Controller, Get } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser } from '@tekcify/auth-backend/nestjs';
@Controller('api')
export class ApiController {
@Get('public')
getPublic() {
return { message: 'Public endpoint' };
}
@Get('protected')
@UseGuards(new JwtAuthGuard({
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
}))
getProtected(@CurrentUser() user: UserPayload) {
return {
message: 'Protected endpoint',
user: user.userId,
};
}
}Step 5: Access User Information
The @CurrentUser() decorator provides access to the authenticated user:
@Get('me')
@UseGuards(JwtAuthGuard)
getCurrentUser(@CurrentUser() user: UserPayload) {
return {
userId: user.userId,
email: user.email,
scopes: user.scopes || [],
};
}Express Integration
Step 1: Install Dependencies
pnpm add @tekcify/auth-backend expressStep 2: Create Auth Middleware
import express from 'express';
import { createAuthMiddleware } from '@tekcify/auth-backend/express';
const app = express();
const authMiddleware = createAuthMiddleware({
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
getUserInfo: async (userId: string) => {
// Optional: Fetch user info from your database
const user = await userRepository.findById(userId);
return user ? { email: user.email } : null;
},
});
app.use(express.json());
// Apply middleware to all /api routes
app.use('/api', authMiddleware);
app.get('/api/profile', (req, res) => {
// req.user is now available
res.json({
userId: req.user!.userId,
email: req.user!.email,
scopes: req.user!.scopes,
});
});Step 3: Route-Level Middleware
You can also apply middleware to specific routes:
app.get('/api/public', (req, res) => {
res.json({ message: 'Public endpoint' });
});
app.get('/api/protected', authMiddleware, (req, res) => {
res.json({
message: 'Protected endpoint',
user: req.user!.userId,
});
});Step 4: TypeScript Support
The middleware extends Express's Request type:
import type { Request, Response } from 'express';
app.get('/api/user', authMiddleware, (req: Request, res: Response) => {
// TypeScript knows req.user exists
const userId = req.user!.userId;
const email = req.user!.email;
res.json({ userId, email });
});Token Verification
For cases where you need to verify tokens directly (e.g., in background jobs, WebSocket connections):
import { verifyAccessToken } from '@tekcify/auth-backend';
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new Error('No token provided');
}
const result = verifyAccessToken(token, {
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
});
if (!result.valid) {
throw new Error('Invalid token');
}
console.log('User ID:', result.payload.sub);
console.log('Scopes:', result.payload.scopes);Token Introspection
For cases where you can't verify tokens directly (e.g., different signing keys, remote verification):
import { introspectToken } from '@tekcify/auth-core-client';
const token = req.headers.authorization?.replace('Bearer ', '');
const result = await introspectToken({
token: token!,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
});
if (result.active) {
console.log('Token is valid');
console.log('User ID:', result.sub);
console.log('Scopes:', result.scope);
} else {
throw new Error('Token is invalid or expired');
}Getting User Information
Fetch user information from the auth server:
import { fetchUserInfo } from '@tekcify/auth-backend';
const userInfo = await fetchUserInfo(accessToken);
console.log('Email:', userInfo.email);
console.log('Name:', userInfo.name);
console.log('Verified:', userInfo.email_verified);User Profile Management
Manage user profiles with simple function calls:
Get User Profile
import { getUserProfile } from '@tekcify/auth-backend';
const profile = await getUserProfile(accessToken);
console.log('User ID:', profile.userId);
console.log('Email:', profile.email);
console.log('Name:', profile.firstName, profile.lastName);
console.log('Avatar:', profile.avatarUrl);Update User Profile
import { updateUserProfile } from '@tekcify/auth-backend';
const updatedProfile = await updateUserProfile(accessToken, {
firstName: 'John',
lastName: 'Doe',
});
console.log('Profile updated:', updatedProfile);Upload Profile Picture
import { uploadProfilePicture } from '@tekcify/auth-backend';
// Browser (with File object)
const fileInput = document.querySelector<HTMLInputElement>('#profilePic');
const file = fileInput?.files?.[0];
if (file) {
const result = await uploadProfilePicture(accessToken, file);
console.log('New avatar URL:', result.avatarUrl);
}
// Node.js (with Buffer)
import fs from 'fs';
const fileBuffer = fs.readFileSync('./avatar.jpg');
const result = await uploadProfilePicture(accessToken, fileBuffer, 'avatar.jpg');
console.log('New avatar URL:', result.avatarUrl);Application Management
Manage OAuth applications with simple function calls:
List Applications
import { listApplications } from '@tekcify/auth-backend';
const apps = await listApplications(accessToken);
apps.forEach(app => {
console.log('App:', app.name);
console.log('Client ID:', app.clientId);
console.log('Logo:', app.logoUrl);
});Get Application by Client ID
import { getApplicationByClientId } from '@tekcify/auth-backend';
// Public endpoint - no auth required
const app = await getApplicationByClientId('your-client-id');
console.log('App Name:', app.name);
console.log('Scopes:', app.scopes);Create Application
import { createApplication } from '@tekcify/auth-backend';
const newApp = await createApplication(accessToken, {
name: 'My Cool App',
description: 'An awesome application',
redirectUris: ['https://myapp.com/callback'],
authorizedOrigins: ['https://myapp.com'],
scopes: ['read:profile', 'write:profile'],
});
console.log('Client ID:', newApp.clientId);
console.log('Client Secret:', newApp.clientSecret); // Save this securely!Update Application
import { updateApplication } from '@tekcify/auth-backend';
await updateApplication(accessToken, 'your-client-id', {
name: 'Updated App Name',
description: 'New description',
});
console.log('Application updated');Upload Application Logo
import { uploadApplicationLogo } from '@tekcify/auth-backend';
// Browser
const logoFile = document.querySelector<HTMLInputElement>('#logo')?.files?.[0];
if (logoFile) {
const result = await uploadApplicationLogo(accessToken, 'your-client-id', logoFile);
console.log('New logo URL:', result.logoUrl);
}
// Node.js
import fs from 'fs';
const logoBuffer = fs.readFileSync('./logo.png');
const result = await uploadApplicationLogo(
accessToken,
'your-client-id',
logoBuffer,
'logo.png'
);
console.log('New logo URL:', result.logoUrl);Delete Application
import { deleteApplication } from '@tekcify/auth-backend';
await deleteApplication(accessToken, 'your-client-id');
console.log('Application deleted');Complete NestJS Example
import { Module, Controller, Get, UseGuards } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard, CurrentUser } from '@tekcify/auth-backend/nestjs';
import type { UserPayload } from '@tekcify/auth-backend/nestjs';
@Module({
providers: [
{
provide: APP_GUARD,
useFactory: () => {
return new JwtAuthGuard({
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
});
},
},
],
})
export class AppModule {}
@Controller('api')
@UseGuards(JwtAuthGuard)
export class ApiController {
@Get('profile')
getProfile(@CurrentUser() user: UserPayload) {
return {
userId: user.userId,
email: user.email,
scopes: user.scopes || [],
};
}
@Get('posts')
async getPosts(@CurrentUser() user: UserPayload) {
// Only return posts for the authenticated user
return await postRepository.findByUserId(user.userId);
}
}Complete Express Example
import express from 'express';
import { createAuthMiddleware, getUserProfile, updateUserProfile } from '@tekcify/auth-backend';
const app = express();
app.use(express.json());
const authMiddleware = createAuthMiddleware({
secret: process.env.JWT_ACCESS_SECRET!,
issuer: 'tekcify-auth',
audience: 'tekcify-api',
});
// Public routes
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Protected routes
app.use('/api', authMiddleware);
app.get('/api/profile', async (req, res) => {
try {
const accessToken = req.headers.authorization?.replace('Bearer ', '');
const profile = await getUserProfile(accessToken!);
res.json(profile);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.put('/api/profile', async (req, res) => {
try {
const accessToken = req.headers.authorization?.replace('Bearer ', '');
const updated = await updateUserProfile(accessToken!, req.body);
res.json(updated);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Complete Integration Example
Here's a complete example showing authentication, profile management, and application management:
import {
OAuthClient,
generateCodeVerifier,
generateCodeChallenge,
} from '@tekcify/auth-core-client';
import {
getUserProfile,
updateUserProfile,
uploadProfilePicture,
listApplications,
createApplication,
uploadApplicationLogo,
} from '@tekcify/auth-backend';
// Step 1: Authenticate user (using OAuth flow)
const oauthClient = new OAuthClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/callback',
scopes: ['read:profile', 'write:profile'],
});
// Generate PKCE parameters
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier, 'S256');
// Build auth URL and redirect user
const authUrl = await oauthClient.buildAuthorizeUrl({
state: crypto.randomUUID(),
codeChallenge: challenge,
codeChallengeMethod: 'S256',
});
// After callback, exchange code for tokens
const tokens = await oauthClient.exchangeCode(code, verifier);
const accessToken = tokens.accessToken;
// Step 2: Manage user profile
const profile = await getUserProfile(accessToken);
console.log('Current profile:', profile);
// Update profile
const updated = await updateUserProfile(accessToken, {
firstName: 'John',
lastName: 'Doe',
});
console.log('Updated profile:', updated);
// Upload profile picture (browser)
const fileInput = document.querySelector<HTMLInputElement>('#profilePic');
if (fileInput?.files?.[0]) {
const result = await uploadProfilePicture(accessToken, fileInput.files[0]);
console.log('New avatar URL:', result.avatarUrl);
}
// Step 3: Manage applications
const apps = await listApplications(accessToken);
console.log('My applications:', apps);
// Create new application
const newApp = await createApplication(accessToken, {
name: 'My New App',
description: 'A cool application',
redirectUris: ['https://myapp.com/callback'],
scopes: ['read:profile'],
});
console.log('Created app:', newApp.clientId);
console.log('Client secret (save this!):', newApp.clientSecret);
// Upload application logo
const logoInput = document.querySelector<HTMLInputElement>('#logo');
if (logoInput?.files?.[0]) {
const logoResult = await uploadApplicationLogo(
accessToken,
newApp.clientId,
logoInput.files[0]
);
console.log('Logo uploaded:', logoResult.logoUrl);
}API Reference
NestJS
JwtAuthGuard
Guard class for protecting routes.
new JwtAuthGuard({
secret: string; // JWT secret
issuer?: string; // Token issuer (default: 'tekcify-auth')
audience?: string; // Token audience (default: 'tekcify-api')
getUserInfo?: (userId: string) => Promise<{ email: string } | null>;
})@CurrentUser()
Parameter decorator to inject the current user.
@CurrentUser() user: UserPayloadExpress
createAuthMiddleware(options)
Creates Express middleware for authentication.
createAuthMiddleware({
secret: string;
issuer?: string;
audience?: string;
getUserInfo?: (userId: string) => Promise<{ email: string } | null>;
})User Profile Functions
getUserProfile(accessToken)
Get the authenticated user's profile.
getUserProfile(accessToken: string): Promise<UserProfile>updateUserProfile(accessToken, data)
Update the user's profile information.
updateUserProfile(
accessToken: string,
data: UpdateProfileDto
): Promise<UserProfile>uploadProfilePicture(accessToken, file, fileName?)
Upload a profile picture. Works with both File (browser) and Buffer (Node.js).
uploadProfilePicture(
accessToken: string,
file: File | Buffer,
fileName?: string
): Promise<UploadResponse>Application Management Functions
listApplications(accessToken)
Get all applications owned by the authenticated user.
listApplications(accessToken: string): Promise<Application[]>getApplicationByClientId(clientId)
Get public application information (no auth required).
getApplicationByClientId(clientId: string): Promise<Application>createApplication(accessToken, data)
Create a new OAuth application.
createApplication(
accessToken: string,
data: CreateApplicationDto
): Promise<CreateApplicationResponse>updateApplication(accessToken, clientId, data)
Update an existing application.
updateApplication(
accessToken: string,
clientId: string,
data: UpdateApplicationDto
): Promise<{ message: string }>uploadApplicationLogo(accessToken, clientId, file, fileName?)
Upload an application logo. Works with both File (browser) and Buffer (Node.js).
uploadApplicationLogo(
accessToken: string,
clientId: string,
file: File | Buffer,
fileName?: string
): Promise<LogoUploadResponse>deleteApplication(accessToken, clientId)
Delete an application.
deleteApplication(
accessToken: string,
clientId: string
): Promise<{ message: string }>Utilities
verifyAccessToken(token, options)
Verifies a JWT access token.
verifyAccessToken(token: string, {
secret: string;
issuer?: string;
audience?: string;
}): VerifiedTokenfetchUserInfo(accessToken)
Fetches user information from the Tekcify auth server using the centralized AUTH_SERVER_URL.
fetchUserInfo(accessToken: string): Promise<UserInfo>Types
interface UserPayload {
userId: string;
email: string;
scopes?: string[];
}
interface VerifiedToken {
payload: TokenPayload;
valid: boolean;
}
interface UserProfile {
userId: string;
email: string;
firstName: string | null;
lastName: string | null;
avatarUrl: string | null;
}
interface UpdateProfileDto {
firstName?: string;
lastName?: string;
}
interface UploadResponse {
avatarUrl: string;
}
interface Application {
clientId: string;
name: string;
description: string | null;
logoUrl: string | null;
redirectUris: string[];
authorizedOrigins: string[] | null;
scopes: string[];
createdAt: string;
updatedAt: string;
}
interface CreateApplicationDto {
name: string;
description?: string;
redirectUris: string[];
authorizedOrigins?: string[];
scopes: string[];
}
interface CreateApplicationResponse extends Application {
clientSecret: string; // Only returned once!
}
interface UpdateApplicationDto {
name?: string;
description?: string;
redirectUris?: string[];
authorizedOrigins?: string[];
scopes?: string[];
}
interface LogoUploadResponse {
logoUrl: string;
}Error Handling
All middleware and guards throw UnauthorizedException (NestJS) or return 401 status (Express) when:
- Token is missing
- Token is invalid
- Token is expired
- User not found (if
getUserInfois provided)
Security Best Practices
- Never expose JWT secrets - Keep secrets in environment variables
- Use HTTPS in production - Always use secure connections
- Validate token issuer and audience - Prevents token reuse across services
- Implement rate limiting - Protect against brute force attacks
- Log authentication failures - Monitor for suspicious activity
- Use short-lived tokens - Refresh tokens regularly
Troubleshooting
"Invalid token" errors
- Verify
JWT_ACCESS_SECRETmatches the auth server - Check token hasn't expired
- Ensure issuer and audience match
"User not found" errors
- Verify
getUserInfofunction returns correct format - Check database connection
- Ensure user exists in your system
Token verification fails
- Verify token format (should start with "Bearer ")
- Check token hasn't been tampered with
- Ensure token type is "access" (not "refresh")
License
MIT
