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
.
$ npm install @femike/swagger-protect
$ yarn add @femike/swagger-protect
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_token
key setted above on succesfuly login.
Or use
@femike/swager-protect-ui
see below.
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()
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
,swaggerPath
loginPath
andcookieKey
have no effect in moduleSwaggerProtect
when 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
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,
swaggerPath
meight beRegExp
it can protect not onlyswagger openapi UI
.
Warning 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 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 })
...
info Hint If
useUI
options is not disabled, module creates controller with answered path/login-api
onGET
request redirect to staticindex.html
UI onPOST
passed data to callback function or injected class implemented fromSwaggerLoginInterface
response 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-->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|\/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 |
See full examples https://github.com/femike/swagger-protect/tree/main/samples.
$ npm i @femike/swagger-protect-ui
$ yarn add @femike/swagger-protect-ui
Default url /login-api
info Hint UI have no settings, it must be only disabled by options
useUI
:false
inforRoot()
orforRootAsync()
Form sendPOST
request to/login-api
with data{ login, password }
on response set Cookie with default keyswagger_token
- 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