@femike/swagger-protect v9.0.3
Description
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.
Installation
$ npm install @femike/swagger-protect$ yarn add @femike/swagger-protectSwagger protect Fastify hook
Easy 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({
    cookieGuard: (
      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\/(json|static\/index.html)(?:\/)?$/, // 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({
    cookieGuard: 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
info Hint 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.
Swagger protect Express middleware
Warning 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()Swagger protect NestJS Module for Express
Warning Warning Express have no method override exists routes we must register protect middleware before setup Swagger.
// 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
}info Hint Parrameters
guard,swaggerPathloginPathandcookieKeyhave no effect in moduleSwaggerProtectwhen we useexpress.
// ./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
Swagger protect NestJS Module for Fastify
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
info Hint Fastify middleware give little bit more than Express,
swaggerPathmeight beRegExpit can protect not onlyswagger openapi UI.Warning 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 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 })
  ...info Hint If
useUIoptions is not disabled, module creates controller with answered path/login-apionGETrequest redirect to staticindex.htmlUI onPOSTpassed data to callback function or injected class implemented fromSwaggerLoginInterfaceresponse pass data to UI where on success setted Cookie.
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-->OPENAPIAPI Spec
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\|\/static\/index\.html)?$ 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 | 
Examples
See full examples https://github.com/femike/swagger-protect/tree/main/samples.
UI
Installation
$ npm i @femike/swagger-protect-ui$ yarn add @femike/swagger-protect-uiDefault url /login-api
info Hint UI have no settings, it must be only disabled by options
useUI:falseinforRoot()orforRootAsync()Form sendPOSTrequest to/login-apiwith data{ login, password }on response set Cookie with default keyswagger_token
Roadmap
- Fastify Hook
 - Express Middleware
 - NestJS Module
 - UI - login
 - Example Page UI
 - Sample Express
 - Sample Fastify
 - Sample Nestjs Express
 - Sample Nestjs Fastify
 - Tests e2e express
 - Tests e2e nest-express
 - Tests e2e fastify
 - Tests e2e nest-fastify
 - Units test replaceApi
 - Units tests
 - Github CI
 - Inject Swagger UI Layout