In a microservices world, your Laravel application often handles user authentication (via Sanctum or Passport) and issues a JWT (JSON Web Token). The challenge is securely allowing a separate, downstream service (like a NestJS data processor) to verify that token without needing to access Laravel's database on every single request.
The solution is to use the JWT verification public key, a pattern known as JWKS (JSON Web Key Set).
1. The Core Security Principle
Instead of the NestJS service asking Laravel: "Is this token valid?", the NestJS service asks: "Does the cryptographic signature of this token match the public key provided by Laravel?"
Laravel (Issuer): Uses its private key to sign the JWT, ensuring integrity.
NestJS (Verifier): Uses Laravel's public key (usually exposed via a public endpoint) to cryptographically verify the token's signature.
This process is fast, stateless, and secure.
2. Laravel Setup (The Issuer)
If you are using Laravel Passport, it already exposes the necessary public key. If you are using Sanctum, you may need a small package to expose the JWKS endpoint, but the principle is the same: the public key must be accessible.
The key file is typically found at: storage/oauth-public.key.
3. NestJS Setup (The Verifier)
We will use NestJS's standard @nestjs/passport and @nestjs/jwt strategy. The key is configuring the JwtModule to use the public key for verification.
A. The JWT Verification Strategy
We define a simple Passport Strategy to pull the token from the header and verify it using the public key.
// src/auth/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import * as fs from 'fs';
import * as path from 'path';
// Load the public key from the Laravel API environment
// NOTE: In production, load this key via an environment variable or KMS.
const pathToPublicKey = path.join(__dirname, '..', '..', 'config', 'laravel_public.key');
const publicKey = fs.readFileSync(pathToPublicKey, 'utf8');
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false, // Always respect token expiration
secretOrKey: publicKey, // <-- CRITICAL: Use the Laravel Public Key here
algorithms: ['RS256'], // Use the signing algorithm Laravel Passport uses
});
}
// The payload contains the user information embedded by Laravel
async validate(payload: any) {
if (!payload.user_id) {
throw new UnauthorizedException('Token payload is missing user ID.');
}
// You can now access the user ID via req.user.user_id in your controllers
return payload;
}
}
B. The Auth Module
We create an AuthModule to register the strategy and make it available application-wide.
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
providers: [JwtStrategy],
exports: [PassportModule],
})
export class AuthModule {}
4. Final Step: Securing Controllers
In any NestJS controller that requires a valid, verified token issued by your Laravel API, simply apply the AuthGuard.
// src/user-data/user-data.controller.ts
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('user-data')
export class UserDataController {
// Use the default 'jwt' strategy defined in AuthModule
@UseGuards(AuthGuard('jwt'))
@Get('profile-status')
getProfileStatus(@Req() req) {
// If execution reaches here, the token signature was verified by the public key.
const userId = req.user.user_id; // Access the user ID from the validated payload
return { status: `Data for user ${userId}` };
}
}
Let me know your suggestions about this post.
Top comments (0)