- Fastify Hook
- Express Middleware
- NestJS For Express
- NestJS For Fastify
- API Spec
- Examples
- UI Module
- Known Issues
- Roadmap
A small tool to protect access to the openapi user interface. Creates a mechanism for checking the request URL: / api / * and checks for the existence of a Cookie swagger_token, if a cookie is present, checks its validity through a callback, in case of failure, redirects to the authorization page /login-api/index.html?backUrl=/path/to/openapi/ui. After successfuly authorization, returns to the backUrl.
^2.0.1Supports: Expressv4.17, Fastifyv3, NestJSv7-v8^9.0.3Supports: Expressv4.18, Fastifyv4, NestJSv9
npm install @femike/swagger-protect@^2.0.1 # or @^9.0.3 for nestjs v9yarn add @femike/swagger-protect@^2.0.1Easy way to protect swagger with fastify use a hook.
// ./src/main.ts
import { fastifyProtectSwagger } from '@femike/swagger-protect'
import { getConnection } from 'typeorm'
import { TokenEntity } from 'your-application/src/entities'
// With clean fastify application
fastify.addHook(
'onRequest',
fastifyProtectSwagger({
guard: (
token, // must return boolean result (token: string) => Promise<boolean> for example typeorm find token on fail return false
) =>
getConnection()
.getRepository(TokenEntity)
.findOneOrFail(token)
.then(t => t.token === token),
cookieKey: 'swagger_token', // key must be stored in cookies on login.
swaggerPath: 'api', // entry point will be protect with guard above.
loginPath: '/login-api', // redirect on fail guard.
}),
)
// For NestJS With Fastify as Adapter hook for module see below.
fastifyAdapter.getInstance().addHook(
'onRequest',
fastifyProtectSwagger({
guard: token =>
getConnection()
.getRepository(TokenEntity)
.findOneOrFail(token)
.then(t => t.token === token),
cookieKey: 'swagger_token',
swaggerPath: /^\/api\/(json|static\/index.html)(?:\/)?$/,
loginPath: '/login-api',
}),
)When guard return true, hook go to the next way and show swagger open api page.
If guard return false, user will be redirected to the page /login-api
Note Your must create frontend application with sign-in form and set cookie with
swagger_tokenkey setted above on succesfuly login.
Or use
@femike/swager-protect-uisee below.
Warning Cookie-parser must be import before setup protect middleware.
// ./src/main.ts
import { expressProtectSwagger } from '@femike/swagger-protect'
import express from 'express'
import { createSwagger } from './swagger'
import cookieParser from 'cookie-parser'
const app = express()
app.get('/', (req, res) => res.send('Home Page <a href="/api">API</a>'))
async function bootstrap() {
app.use(cookieParser()) // @!important need set cookie-parser before setup protect middleware
expressProtectSwagger({
guard: (token: string) => !!token, // if token exists access granted!
})
createSwagger(app).listen(3000, () => {
console.log(`Application is running on: http://localhost:3000`)
})
}
bootstrap()Warning
Expresshave no methods override exists routes, we must register middleware before setupSwagger.
// touch ./src/swagger/config.ts
import { registerExpressProtectSwagger } from '@femike/swagger-protect'
import type { INestApplication } from '@nestjs/common'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { SwaggerGuard } from './guard'
export const SWAGGER_PATH = 'api'
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.addBearerAuth()
.build()
export function createSwagger(app: INestApplication): INestApplication {
registerExpressProtectSwagger(app, {
guard: new SwaggerGuard(),
swaggerPath: SWAGGER_PATH,
loginPath: '/login-api',
cookieKey: 'swagger_token',
})
const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup(SWAGGER_PATH, app, document)
return app
}Note Parrameters
guard,swaggerPathloginPathandcookieKeyhave no effect in moduleSwaggerProtectwhen we usingExpress.
// ./src/main.ts
import { SwaggerProtect } from '@femike/swagger-protect'
import { Module } from '@nestjs/common'
import { CatsModule } from './cats/cats.module'
import { SwaggerLogin } from './swagger'
@Module({
imports: [
CatsModule,
SwaggerProtect.forRoot({
guard: () => false, // no effect on express
logIn: new SwaggerLogin(),
swaggerPath: 'api', // no effect on express
loginPath: '/login-api', // no effect on express
cookieKey: 'swagger_token', // no effect on express
}),
],
})
export class AppModule {}// $ touch ./src/swagger/swagger.login.ts
import {
SwaggerProtectLogInDto,
SwaggerLoginInterface,
} from '@femike/swagger-protect'
import { v4 as uuid } from 'uuid'
/**
* Swagger Login
*/
export class SwaggerLogin implements SwaggerLoginInterface {
async execute({
login,
password,
}: SwaggerProtectLogInDto): Promise<{ token: string }> {
return login === 'admin' && password === 'changeme'
? { token: uuid() }
: { token: '' }
}
}Example login service must be implemented from SwaggerLoginInterface
Create class guard must be implemented from SwaggerGuardInterface
// $ touch ./src/swagger/swagger.guard.ts
import type { SwaggerGuardInterface } from '@femike/swagger-protect'
import { Inject } from '@nestjs/common'
import { AuthService } from '../auth'
/**
* Swagger Guard
*/
export class SwaggerGuard implements SwaggerGuardInterface {
constructor(@Inject(AuthService) private readonly service: AuthService) {}
/**
* Method guard
*/
async canActivate(token: string): Promise<boolean> {
return await this.service.method(token)
}
}Now register module SwaggerProtect
Note
Fastifymiddleware give little bit more thanExpress,swaggerPathmeight beRegExpIt can protect not onlyswagger openapi UI.
Warning But remember if you override this option you must protect two entry points
/api/jsonand/api/static/index.htmlin thisRegExp
// ./src/app.module.ts
import { LoggerModule } from '@femike/logger'
import { Module } from '@nestjs/common'
import { SwaggerProtect } from '@femike/swagger-protect'
@Module({
imports: [
LoggerModule,
SwaggerProtect.forRootAsync<'fastify'>({
// <- pass
imports: [AuthModule],
useFactory: () => ({
guard: SwaggerGuard,
logIn: SwaggerLogin,
swaggerPath: /^\/api\/(json|static\/index.html)(?:\/)?$/,
useUI: true, // switch swagger-protect-ui
}),
}),
],
controllers: [AppController],
providers: [HttpStrategy, AppService, AppLogger],
})
export class AppModule {}Warning The controller
login-apiusesClassSerializeryou have to addValidationPipeand container for fallback errors.
// ./src/main.ts
...
app.useGlobalPipes(
new ValidationPipe({
transform: true,
disableErrorMessages: false,
enableDebugMessages: true,
}),
)
useContainer(app.select(AppModule), { fallbackOnErrors: true })
...Note If
useUIoptions is not disabled, module creates controller with answered path/login-apionGETrequest redirect to staticindex.htmlUIonPOSTrequest passed data to callback function or injected class implemented fromSwaggerLoginInterfaceresponse return data toUIwhere on success setCookie.
graph TD;
REQUEST-->GUARD_CALLBACK-->COOKIE_VALIDATE
COOKIE_VALIDATE-->LOGIN_UI
COOKIE_VALIDATE-->OPENAPI
LOGIN_UI-->POST_REQUEST_AUTH
POST_REQUEST_AUTH-->LOGIN_UI
LOGIN_UI-->SET_COOKIE
SET_COOKIE-->COOKIE_VALIDATE
SET_COOKIE-->BACK_URL
BACK_URL-->OPENAPI
The forRoot() method takes an options object with a few useful properties.
| Property | Type | Description |
|---|---|---|
guard |
Function / Class | Function or Class guard must be return boolean result. Class meight be implemented SwaggerGuardInterface. Default: (token: string) => !!token |
logIn |
Function / Class | Function or Class logIn must return object with key token. Class meight be implemented SwaggerLoginInterface. Default: () => ({ token: '' }) |
swaggerPath? |
string / RegExp | Default: RegExp /^\/api(?:\/|-json|\/json|\/static.+|\/swagger.+)?$/ for fastify |
loginPath? |
string | Path where user will be redirect on fail guard. Default /login-api |
cookieKey? |
string | Key name stored in Cookie. Default swagger_token |
useUI? |
Boolean | Use or not user interface for login to swagger ui. When loginPath was changed from /login-api ui will be disabled. Default true |
Note See full examples on
Github@femike/swagger-protect/tree/main/samples
npm i @femike/swagger-protect-uiyarn add @femike/swagger-protect-uiDefault url: /login-api
Note UI have no settings, it can be only disabled with options
useUI:falseinforRoot()orforRootAsync()Form sendPOSTrequest to/login-apiwith data{ login, password }on response set Cookie with default keyswagger_token
Warning If you want to use global prefix dont forget set exclude path
login-api
app.setGlobalPrefix('api/v1', {
exclude: ['login-api'],
})- Fastify Hook
- Express Middleware
- NestJS Module
- UI - login
- Example Page UI
- Sample fastify
- Sample express
- Sample nestjs fastify
- Tests e2e nest-fastify
- Tests e2e nest-express
- Tests e2e express
- Tests e2e fastify
- Units test replaceApi
- Units tests
- Github CI
- Inject Swagger UI Layout
