Mercury Auth is a flexible authentication module for NestJS applications that provides multiple authentication strategies:
- JWT Authentication - Token-based authentication with access and refresh tokens
- API Key Authentication - For server-to-server communication and integrations
- Basic Authentication - For internal/admin routes
- Impersonation - For debugging and support scenarios
- Support for both
FastifyAdapter
andExpressAdapter
- Flexible token delivery via cookies, headers, or both
- Refresh token rotation
- API Key authentication
- Role-based access control
- GraphQL support
- Event-driven architecture for auth events
npm install --save @mercury-labs/nest-auth
Learn more about the relevant package @mercury-labs/nest-hashing
Mercury Auth provides multiple authentication strategies that can be used independently or together:
The primary authentication method using JSON Web Tokens:
- Access Token: Short-lived token for API access
- Refresh Token: Long-lived token to obtain new access tokens
- Delivery Methods: Bearer token in headers, cookies, or both
Simple key-based authentication for server-to-server communication:
- Single token passed via the
Api-Key
header - No expiration handling (managed by your application)
- Ideal for service accounts, integrations, and automated processes
HTTP Basic Authentication for internal or admin routes:
- Username and password encoded in the
Authorization
header - Configured via the
basicAuth
property in your auth definitions - Applied using the
@InternalOnly()
decorator
Allows administrators to log in as other users for support purposes:
- Special login format:
{cipher}username
with impersonation password - Configurable via the
impersonate
property in your auth definitions - Can be enabled/disabled as needed
- Login: User provides credentials and receives access and refresh tokens
- API Access: User includes access token with requests
- Token Refresh: When access token expires, user uses refresh token to get a new one
- Logout: User invalidates their tokens
Complete sample applications demonstrating different adapters and configurations:
import {
AUTH_PASSWORD_HASHER,
AuthRepository,
IAuthUserEntity,
PasswordHasherService
} from '@mercury-labs/nest-auth'
import { Injectable } from '@nestjs/common'
import moment from 'moment'
import { Observable, of } from 'rxjs'
export class SampleAuthRepository implements AuthRepository<string, AuthDto> {
/**
* Your own throttle service
* Sample demo
*/
private _throttleAuthService: ThrottleAuthService
public constructor(
@InjectPasswordHasher()
protected readonly hasher: PasswordHasherService,
protected readonly moduleRef: ModuleRef
) {
}
public onModuleInit(): void {
this._throttleAuthService = this.moduleRef.get(ThrottleAuthService)
}
public getAuthUserByUsername(
username: string
): Observable<IAuthUserEntity | undefined> {
return of({
id: 'some-random-id',
username: 'sample-user@gmail.com',
email: 'sample-user@gmail.com',
password: 'some-password-hash',
})
}
public authenticate(
username: string,
request: AuthDto,
impersonated: boolean
): Observable<IAuthUserEntity | undefined> {
/**
* Ignore the other checks if impersonated
*/
if (impersonated) {
return this.getAuthUserByUsername(username)
}
/**
* Do some additional logics
*/
return this._throttleAuthService.checkAlreadyBlockedAccount(username).pipe(
mergeMap(() => this.getAuthUserByUsername(username))
)
}
public getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
public getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
}
The repository demo was built on top of RxJS, but you can also define it with Promise
- async
/await
.
For example:
@Injectable()
export class SampleAuthRepository implements AuthRepository<string, AuthDto> {
public constructor(
@InjectPasswordHasher()
protected readonly hasher: PasswordHasherService
) {}
public async getAuthUserByUsername(
username: string
): Promise<IAuthUserEntity | undefined> {
return {
id: _.random(1, 1999).toString(),
username: 'sample-user@gmail.com',
email: 'sample-user@gmail.com',
password: await this.hasher.hash('testLogin@12345'),
}
}
public async authenticate(
username: string,
request: AuthDto,
impersonated: boolean
): Promise<IAuthUserEntity | undefined> {
const user = await this.getAuthUserByUsername(username)
if (impersonated) {
return user
}
/**
* Do some additional logics
*/
return user
}
public async getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Promise<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
public async getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Promise<IAuthUserEntity | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
}
import { AuthModule, AuthTransferTokenMethod } from '@mercury-labs/nest-auth'
import { ConfigService } from '@nestjs/config'
import { Module } from '@nestjs/common'
@Module({
imports: [
AuthModule.forRootAsync({
/**
* Use default local auth or not?
* For example, you're using GraphQL and want to disable default HTTP auth
*/
useLocalAuth: true,
global: true,
definitions: {
useFactory: (config: ConfigService) => {
return {
basicAuth: {
username: config.get('BASIC_AUTH_USER'),
password: config.get('BASIC_AUTH_PASSWORD'),
},
impersonate: {
isEnabled: config.get('AUTH_IMPERSONATE_ENABLED') === 'true',
cipher: config.get('AUTH_IMPERSONATE_CIPHER'),
password: config.get('AUTH_IMPERSONATE_PASSWORD'),
},
jwt: {
secret: config.get('AUTH_JWT_SECRET'),
expiresIn: envRequired('AUTH_JWT_EXPIRES') || '1h',
refreshTokenExpiresIn: envRequired('AUTH_JWT_REFRESH_EXPIRES') || '7d',
},
transferTokenMethod: config.get<AuthTransferTokenMethod>(
'AUTH_TRANSFER_TOKEN_METHOD'
),
redactedFields: ['password'],
hashingSecretKey: config.get('HASHING_SECRET_KEY') || '',
usernameField: 'username',
passwordField: 'password',
httpAdaptorType: 'fastify'
}
},
inject: [ConfigService],
},
/**
* In case you want to register some providers inside AuthModule
* These providers will be also exported from AuthModule
*/
otherProviders: [],
authRepository: {
useFactory: (hasher: PasswordHasherService) => {
return new CmsAuthRepository(hasher)
},
inject: [AUTH_PASSWORD_HASHER]
}
}),
]
})
export class AppModule {
}
interface IAuthDefinitions {
/**
* Configuration for basic auth
*/
basicAuth: {
username: string
password: string
/**
* The realm name for WWW-Authenticate header
*/
realm?: string
}
/**
* Configuration for JWT
*/
jwt: {
/**
* Do not expose this key publicly.
* We have done so here to make it clear what the code is doing,
* but in a production system you must protect this key using appropriate measures,
* such as a secrets vault, environment variable, or configuration service.
*/
secret: string
/**
* Expressed in seconds or a string describing a time span zeit/ms.
* @see https://github.com/vercel/ms
* Eg: 60, “2 days”, “10h”, “7d”
*/
expiresIn: string | number
refreshTokenExpiresIn: string | number
}
/**
* Configuration for impersonate login
* You can login to a user account without their password.
* Eg:
* - username: {your_impersonate_cipher_key}username
* - password: {your_impersonate_password}
*/
impersonate?: {
isEnabled: boolean
cipher: string
password: string
}
/**
* Hide some sensitive fields while getting user profile.
*/
redactedFields?: string[]
/**
* These routes will always be PUBLIC.
* No authentication required.
*/
ignoredRoutes?: string[]
enableHashingToken?: boolean
/**
* Used to encode/decode the access/refresh token
* 32 characters string
*/
hashingSecretKey?: string
/**
* We accepted these 3 values: cookie|bearer|both
* - cookie: after user login, their accessToken and refreshToken will be sent using cookie
* - bearer: after user login, their accessToken and refreshToken will be sent to response body
* - both: mixed those 2 above values.
*/
transferTokenMethod: AuthTransferTokenMethod,
cookieOptions?: {
domain?: string
path?: string // Default '/'
sameSite?: boolean | 'lax' | 'strict' | 'none' // Default true
signed?: boolean
httpOnly?: boolean // Default true
secure?: boolean
},
/**
* Username field when login
* Eg: email, username,...
*/
usernameField?: string
/**
* Password field when login
* Eg: password, pass...
*/
passwordField?: string,
httpAdaptorType: 'fastify' | 'express'
}
By default, I use pbkdf2
to encode and compare password hash.
In some case, you might need to change the way or algorithm to hash the
password.
This example uses bcrypt
to hash and compare password phrase.
import { Injectable } from '@nestjs/common'
import { compare, hash } from 'bcrypt'
import { PasswordHasherService } from './password-hasher.service'
@Injectable()
export class BcryptPasswordHasherService
implements PasswordHasherService<string>
{
public async hash(password: string): Promise<string> {
return hash(password, 10)
}
public async compare(
password: string,
hashedPassword: string
): Promise<boolean> {
return compare(password, hashedPassword)
}
}
AuthModule.forRootAsync({
...,
passwordHasher: {
useFactory: () => {
return new BcryptPasswordHasherService()
},
}
})
import { Injectable } from '@nestjs/common'
import { ModuleRef } from '@nestjs/core'
import { mergeMap, Observable, of } from 'rxjs'
import {
AuthDto,
AuthRepository,
IAuthUserEntity,
InjectPasswordHasher,
PasswordHasherService,
} from '@mercury-labs/nest-auth'
@Injectable()
export class SampleAuthRepository implements AuthRepository<IPbkdf2Hash, AuthDto> {
/**
* Your own throttle service
* Sample demo
*/
private _throttleAuthService: ThrottleAuthService
public constructor(
@InjectPasswordHasher()
protected readonly hasher: PasswordHasherService,
protected readonly moduleRef: ModuleRef
) {
}
public onModuleInit(): void {
this._throttleAuthService = this.moduleRef.get(ThrottleAuthService)
}
public getAuthUserByUsername(
username: string
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
return of({
id: _.random(1, 1999).toString(),
username: 'sample-user@gmail.com',
email: 'sample-user@gmail.com',
password: {
hash: 'some-hash',
salt: 'some-salt'
},
})
}
public authenticate(
username: string,
request: AuthDto,
impersonated: boolean
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
/**
* Ignore the other checks if impersonated
*/
if (impersonated) {
return this.getAuthUserByUsername(username)
}
/**
* Do some additional logics
*/
return this._throttleAuthService.checkAlreadyBlockedAccount(username).pipe(
mergeMap(() => this.getAuthUserByUsername(username))
)
}
public getAuthUserByAccessToken(
accessToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
public getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Observable<IAuthUserEntity<IPbkdf2Hash> | undefined> {
/**
* You can check the token if it's stored in database.
*/
return this.getAuthUserByUsername(jwtPayload.username)
}
}
curl
curl --request POST \
--url http://localhost:4005/auth/login \
--header 'Content-Type: application/json' \
--data '{
"username": "sample-email+dev@gmail.com",
"password": "some-password-phrase"
}'
When your access token expires, you can use the refresh token to get a new access token without requiring the user to log in again. The refresh token has a longer expiration time than the access token (configured via refreshTokenExpiresIn
in your JWT configuration).
The package provides a built-in endpoint for refreshing tokens at /auth/refresh-token
. You need to include your refresh token in the request.
curl --request POST \
--url http://localhost:4005/auth/refresh-token \
--header 'Refresh-Token: eyJpdiI6IjFmNTY4ZWZmN2RmODRmZjkxNjQx...'
The refresh token is automatically included in the cookie, so you just need to make the request:
curl --request POST \
--url http://localhost:4005/auth/refresh-token \
--cookie "refreshToken=eyJpdiI6IjFmNTY4ZWZmN2RmODRmZjkxNjQx..."
The response will include a new access token but not a new refresh token:
{
"userData": {
"id": "user-id",
"username": "sample-user@gmail.com",
"email": "sample-user@gmail.com"
},
"token": {
"accessToken": "new-access-token",
"expiryDate": "2023-01-01T00:00:00.000Z"
}
}
curl
curl --request GET \
--url http://localhost:4005/auth/profile \
--header 'Authorization: Bearer eyJpdiI6IjFmNTY4ZWZmN2RmODRmZjkxNjQx...'
curl
curl --request POST \
--url http://localhost:4005/auth/logout
--header 'Authorization: Bearer eyJpdiI6IjFmNTY4ZWZmN2RmODRmZjkxNjQx...'
@InjectAuthDefinitions()
: inject IAuthDefinitions
to your injectable
classes.
@InjectPasswordHasher()
: inject PasswordHasherService
to your injectable
classes. This is an alias for the statement below:
import { Inject, Injectable } from '@nestjs/common'
import { AUTH_PASSWORD_HASHER } from '@mercury-labs/nest-auth'
@Injectable()
export class SampleService {
public constructor(
@Inject(AUTH_PASSWORD_HASHER)
protected readonly hasher: PasswordHasherService
) {}
}
@Public()
This decorator will help your controller available for all users. No
authentication required.
import { Public } from '@mercury-labs/nest-auth'
import { Controller, Get } from '@nestjs/common'
@Controller()
@Public()
export class AppController {
@Get()
public getHello(): string {
return 'Hello World!'
}
}
@InternalOnly()
You need to use basic auth while accessing your controller.
import { InternalOnly } from '@mercury-labs/nest-auth'
import { Controller, Get } from '@nestjs/common'
@Controller()
@InternalOnly()
export class AppController {
@Get()
public getHello(): string {
return 'Hello World!'
}
}
@AuthApiKey()
This decorator applies the API Key authentication guard to your controller or route. It requires an Api-Key
header to be present in the request.
import { AuthApiKey } from '@mercury-labs/nest-auth'
import { Controller, Get } from '@nestjs/common'
@Controller()
@AuthApiKey()
export class AppController {
@Get()
public getHello(): string {
return 'Hello World!'
}
}
You can also apply the decorator to specific routes instead of the entire controller:
import { AuthApiKey, CurrentUser } from '@mercury-labs/nest-auth'
import { Controller, Get } from '@nestjs/common'
import { ApiOperation, ApiTags } from '@nestjs/swagger'
@ApiTags('Authentication')
@Controller({ path: 'auth' })
export class ApiKeyController {
@ApiOperation({
summary: 'Get profile by API Key',
})
@AuthApiKey()
@Get('profile/api-key')
public profileByApiKey(
@CurrentUser() user: IAuthUserEntityForResponse
): IAuthUserEntityForResponse {
return user
}
// This route uses JWT authentication (default)
@Get('profile')
public profile(
@CurrentUser() user: IAuthUserEntityForResponse
): IAuthUserEntityForResponse {
return user
}
}
@AuthRefreshToken()
This decorator applies the refresh token guard to your controller or route. It's used internally by the refresh token endpoint.
import { Controller, Post, UseInterceptors } from '@nestjs/common'
import { AuthRefreshToken, CurrentUserWithToken } from '@mercury-labs/nest-auth'
import { CookieAuthInterceptor } from '@mercury-labs/nest-auth'
@Controller({ path: 'auth' })
@AuthRefreshToken()
@UseInterceptors(CookieAuthInterceptor)
export class RefreshTokenController {
@Post('refresh-token')
public index(
@CurrentUserWithToken() user: IAuthWithTokenResponse
): Observable<IRefreshTokenAuthResponse> {
// Implementation to return a new access token
// The refresh token is extracted from the request by the guard
}
}
JWT By default, all another routes will be checked using JWT strategy guard.
It means, you need to pass your access token into the request header.
If you set the transfer method to both
or cookie
, you don't need to do
anything. The Authorization
and RefreshToken
already be sent via cookie.
If you set the transfer method to bearer
, you need to pass your access token
to the Authorization
header.
Authorization: Bearer {yourAccessToken}
Refresh-Token: {yourRefreshToken}
@CurrentUser()
This decorator will return the current logged-in user.
import { Controller, Get } from '@nestjs/common'
import { ApiOperation, ApiTags } from '@nestjs/swagger'
import {
IAuthUserEntityForResponse,
CurrentUser
} from '@mercury-labs/nest-auth'
@ApiTags('User details')
@Controller({path: 'users/-'})
export class ProfileController {
@ApiOperation({
summary: 'Get profile',
})
@Get('profile')
public profile(
@CurrentUser() user: IAuthUserEntityForResponse
): IAuthUserEntityForResponse {
return user
}
}
@PublicWithOptionalUser()
Same as @Public()
, but we can get user info
with @CurrentUser()
if you pass your access token into the request header.
import { PublicWithOptionalUser } from '@mercury-labs/nest-auth'
import { Controller, Get } from '@nestjs/common'
@Controller()
@PublicWithOptionalUser()
export class AppController {
@Get()
public getHello(
@CurrentUser() user?: IAuthUserEntityForResponse | undefined
): string {
/**
* user is nullable
*/
console.log({
userId: user?.id
})
return 'Hello World!'
}
}
Triggered when user logged in successfully.
You can store the relevant access/refresh tokens in database using this event.
import { UserLoggedInEvent } from '@mercury-labs/nest-auth'
import { EventsHandler, IEventHandler } from '@nestjs/cqrs'
import { delay, lastValueFrom, of, tap } from 'rxjs'
@EventsHandler(UserLoggedInEvent)
export class UserLoggedInEventHandler implements IEventHandler<UserLoggedInEvent> {
public async handle(event: UserLoggedInEvent): Promise<void> {
await lastValueFrom(
of(event).pipe(
delay(1200),
tap(({user, isImpersonated}) => {
console.log('UserLoggedInEvent', {user, isImpersonated})
})
)
)
}
}
Triggered when a new access token is generated from refresh token.
You can store the relevant new access token in database using this event.
import { AccessTokenGeneratedFromRefreshTokenEvent } from '@mercury-labs/nest-auth'
import { EventsHandler, IEventHandler } from '@nestjs/cqrs'
import { delay, lastValueFrom, of, tap } from 'rxjs'
@EventsHandler(AccessTokenGeneratedFromRefreshTokenEvent)
export class AccessTokenGeneratedFromRefreshTokenEventHandler
implements IEventHandler<AccessTokenGeneratedFromRefreshTokenEvent> {
public async handle(event: AccessTokenGeneratedFromRefreshTokenEvent): Promise<void> {
console.log('AccessTokenGeneratedFromRefreshTokenEvent', event)
}
}
Triggered when user logged out.
import { UserLoggedOutEvent } from '@mercury-labs/nest-auth'
import { EventsHandler, IEventHandler } from '@nestjs/cqrs'
import { delay, lastValueFrom, of, tap } from 'rxjs'
@EventsHandler(UserLoggedOutEvent)
export class UserLoggedOutEventHandler implements IEventHandler<UserLoggedOutEvent> {
public async handle(event: UserLoggedOutEvent): Promise<void> {
console.log('UserLoggedOutEvent', event)
}
}
- You must install package @nestjs/cqrs to work with auth events.
Mercury Auth provides a robust API Key authentication strategy that allows clients to authenticate using an API key instead of JWT tokens. This is particularly useful for server-to-server communication, background jobs, or third-party integrations where the traditional username/password flow isn't appropriate.
- The client includes an API key in the request header (
Api-Key
) - The
ApiKeyStrategy
extracts the API key from the request - The strategy calls the
getAuthUserByApiKey
method on yourAuthRepository
implementation - If a valid user is returned, the request is authenticated
To implement API Key authentication, you need to:
- Implement the
getAuthUserByApiKey
method in yourAuthRepository
:
@Injectable()
export class YourAuthRepository implements AuthRepository<string, AuthDto> {
// Other required methods...
public async getAuthUserByApiKey(
apiKey: string
): Promise<IAuthUserEntity | undefined> {
// Validate the API key against your database or other storage
const user = await this.userRepository.findByApiKey(apiKey);
if (!user) {
return undefined; // Invalid API key
}
return {
id: user.id,
username: user.email,
email: user.email,
// Other user properties
};
}
}
- Apply the
@AuthApiKey()
decorator to controllers or routes that should accept API key authentication:
@Controller('api')
@AuthApiKey()
export class ApiController {
// All routes in this controller require API key authentication
}
Mercury Auth doesn't provide built-in API key generation or management, as these are typically application-specific. Here's a recommended approach:
- Create a service for generating and managing API keys:
@Injectable()
export class ApiKeyService {
constructor(
@InjectRepository(ApiKeyEntity)
private apiKeyRepository: Repository<ApiKeyEntity>,
) {}
public async generateApiKey(userId: string, name: string): Promise<string> {
// Generate a secure random API key
const apiKey = crypto.randomUUID();
// Store it in your database
await this.apiKeyRepository.save({
userId,
name,
key: apiKey,
createdAt: new Date(),
});
return apiKey;
}
public async validateApiKey(apiKey: string): Promise<User | null> {
const apiKeyRecord = await this.apiKeyRepository.findOne({
where: { key: apiKey, isActive: true },
relations: ['user'],
});
return apiKeyRecord?.user || null;
}
public async revokeApiKey(apiKeyId: string): Promise<void> {
await this.apiKeyRepository.update(
{ id: apiKeyId },
{ isActive: false, revokedAt: new Date() }
);
}
}
- Use this service in your
AuthRepository
implementation:
@Injectable()
export class YourAuthRepository implements AuthRepository<string, AuthDto> {
constructor(private apiKeyService: ApiKeyService) {}
public async getAuthUserByApiKey(
apiKey: string
): Promise<IAuthUserEntity | undefined> {
const user = await this.apiKeyService.validateApiKey(apiKey);
if (!user) {
return undefined;
}
return {
id: user.id,
username: user.email,
email: user.email,
// Other user properties
};
}
}
You can test API key authentication using curl:
curl --request GET \
--url http://localhost:4005/auth/profile/api-key \
--header 'Api-Key: your-api-key-here'
- Generate Strong Keys: Use a cryptographically secure random generator
- Store Securely: Never store API keys in plaintext; use hashing
- Set Expirations: Consider adding expiration dates to API keys
- Limit Scope: Assign specific permissions to each API key
- Monitor Usage: Log and monitor API key usage for suspicious activity
- Rotate Regularly: Encourage or enforce regular key rotation
The package provides several interfaces for handling authentication responses:
// Full authentication response with both access and refresh tokens
interface IAuthWithTokenResponse<UserDataType = IAuthUserEntityForResponse> {
userData: UserDataType;
token: IJwtTokenResponse;
}
// Response when refreshing tokens (no new refresh token)
interface IRefreshTokenAuthResponse<UserDataType = IAuthUserEntityForResponse> {
userData: UserDataType;
token: Omit<IJwtTokenResponse, 'refreshToken' | 'refreshTokenExpiryDate'>;
}
// JWT token response structure
interface IJwtTokenResponse {
accessToken: string;
refreshToken: string;
expiryDate: Date;
refreshTokenExpiryDate: Date;
}
For production applications, you should consider storing tokens in your database to enable features like:
- Token revocation
- Tracking active sessions
- Limiting concurrent logins
You can use the UserLoggedInEvent
to store tokens when a user logs in:
@EventsHandler(UserLoggedInEvent)
export class UserLoggedInEventHandler implements IEventHandler<UserLoggedInEvent> {
constructor(private readonly tokenRepository: TokenRepository) {}
public async handle(event: UserLoggedInEvent): Promise<void> {
const { user, accessToken, refreshToken } = event;
// Store tokens in your database
await this.tokenRepository.saveTokens({
userId: user.id,
accessToken,
refreshToken,
// Add other metadata as needed
});
}
}
Then in your AuthRepository
implementation, you can validate tokens against your database:
public async getAuthUserByRefreshToken(
refreshToken: string,
jwtPayload: IJwtPayload
): Promise<IAuthUserEntity | undefined> {
// Check if the token exists and is valid in your database
const tokenRecord = await this.tokenRepository.findByRefreshToken(refreshToken);
if (!tokenRecord || tokenRecord.isRevoked) {
return undefined;
}
// Get the user associated with this token
return this.getAuthUserByUsername(jwtPayload.username);
}
Mercury Auth uses a global guard system that automatically applies the appropriate authentication strategy based on route decorators. Here's how the guards work together:
The AuthGlobalGuard
is registered as a global guard and determines which specific guard to apply based on route decorators:
@Injectable()
export class AuthGlobalGuard implements CanActivate {
// ...
public canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
// Check if route is public
const isPublic = this._reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
])
if (isPublic) {
return true; // Allow access without authentication
}
// Check if route is internal only (basic auth)
const isInternalOnly = this._reflector.getAllAndOverride<boolean>(IS_INTERNAL_ONLY, [
context.getHandler(),
context.getClass()
])
if (isInternalOnly) {
return this._basicAuthGuard.canActivate(context);
}
// Check if route requires API key
const isApiKey = this._reflector.getAllAndOverride<boolean>(IS_API_KEY, [
context.getHandler(),
context.getClass()
])
if (isApiKey) {
return this._authApiKeyGuard.canActivate(context);
}
// Check if route is for refresh token
const isRefreshToken = this._reflector.getAllAndOverride<boolean>(IS_REFRESH_TOKEN_KEY, [
context.getHandler(),
context.getClass()
])
if (isRefreshToken) {
return this._refreshTokenGuard.canActivate(context);
}
// Default to JWT authentication
return this._jwtGuard.canActivate(context);
}
}
When multiple decorators are applied, the guards are evaluated in this order:
-
@Public()
- No authentication required -
@InternalOnly()
- Basic authentication -
@AuthApiKey()
- API Key authentication -
@AuthRefreshToken()
- Refresh token authentication - Default - JWT authentication
Mercury Auth provides GraphQL-specific guards that work with the NestJS GraphQL module:
GraphqlAuthJwtGuard
GraphqlAuthRefreshTokenGuard
GraphqlAuthApiKeyGuard
These guards extract authentication data from the GraphQL context instead of HTTP requests.
- Allow user to revoke
accessToken
,refreshToken
of some user. - Add support for token blacklisting
- Implement token rotation for enhanced security
- Add support for OAuth2 authentication providers