- 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.1
Supports: Expressv4.17
, Fastifyv3
, NestJSv7
-v8
^9.0.3
Supports: Expressv4.18
, Fastifyv4
, NestJSv9
npm install @femike/swagger-protect@^2.0.1 # or @^9.0.3 for nestjs v9
yarn add @femike/swagger-protect@^2.0.1
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({
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_token
key setted above on succesfuly login.
Or use
@femike/swager-protect-ui
see 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
Express
have 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
,swaggerPath
loginPath
andcookieKey
have no effect in moduleSwaggerProtect
when 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
Fastify
middleware give little bit more thanExpress
,swaggerPath
meight beRegExp
It can protect not onlyswagger openapi UI
.
Warning But remember if you override this option you must protect two entry points
/api/json
and/api/static/index.html
in 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-api
usesClassSerializer
you have to addValidationPipe
and 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
useUI
options is not disabled, module creates controller with answered path/login-api
onGET
request redirect to staticindex.html
UI
onPOST
request passed data to callback function or injected class implemented fromSwaggerLoginInterface
response return data toUI
where 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-ui
yarn add @femike/swagger-protect-ui
Default url: /login-api
Note UI have no settings, it can be only disabled with options
useUI
:false
inforRoot()
orforRootAsync()
Form sendPOST
request to/login-api
with 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