Reputation: 2172
I'm currently using Swagger in my NestJS project, and I have the explorer enabled:
in main.js
const options = new DocumentBuilder()
.setTitle('My App')
.setSchemes('https')
.setDescription('My App API documentation')
.setVersion('1.0')
.build()
const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup('docs', app, document, {
customSiteTitle: 'My App documentation',
})
With this, the explorer is accessible in /docs
which is what I expected. But I was wondering if it's possible to add any Authentication layer to the explorer, so only certain requests are accepted.
I want to make this explorer accessible in production, but only for authenticated users.
Upvotes: 74
Views: 88508
Reputation: 1259
Securing access to your Swagger with HTTP Basic Auth using NestJS with Express
First run npm i express-basic-auth
then add the following to your main.{ts,js}
:
import * as basicAuth from "express-basic-auth";
// ...
// Sometime after NestFactory add this to add HTTP Basic Auth
app.use(
// Paths you want to protect with basic auth
"/docs*",
basicAuth({
challenge: true,
users: {
yourUserName: "p4ssw0rd",
},
})
);
// Your code
const options = new DocumentBuilder()
.setTitle("My App")
.setSchemes("https")
.setDescription("My App API documentation")
.setVersion("1.0")
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(
// Make sure you use the same path just without `/` and `*`
"docs",
app,
document,
{
customSiteTitle: "My App documentation",
}
);
// ...
With this in place you will be prompted on any of the /docs
route with a HTTP Basic Auth prompt. We add the *
to also protect the generated JSON (/docs-json
) and YAML (/docs-json
) OpenAPI files. If you have any other route beginning with /docs
, that should not be protected, you should rather explicitly name the routes you want to protect in an array ['/docs', '/docs-json', '/docs-yaml']
.
You should not put the credentials in your code/repository but rather in your .env
and access via the ConfigService.
I have seen this solution first here.
Upvotes: 46
Reputation: 2389
As per the documentation here in case you want to protect access to the swagger page /api-docs
i.e only users with access can view the page have the below code in main.ts
const apiDocumentationCredentials = {
name: 'admin',
pass: 'admin',
};
async function bootstrap() {
const app = await NestFactory.create<INestApplication>(ApplicationModule);
const httpAdapter = app.getHttpAdapter();
httpAdapter.use('/api-docs', (req, res, next) => {
function parseAuthHeader(input: string): { name: string; pass: string } {
const [, encodedPart] = input.split(' ');
const buff = Buffer.from(encodedPart, 'base64');
const text = buff.toString('ascii');
const [name, pass] = text.split(':');
return { name, pass };
}
function unauthorizedResponse(): void {
if (httpAdapter.getType() === 'fastify') {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic');
} else {
res.status(401);
res.set('WWW-Authenticate', 'Basic');
}
next();
}
if (!req.headers.authorization) {
return unauthorizedResponse();
}
const credentials = parseAuthHeader(req.headers.authorization);
if (
credentials?.name !== apiDocumentationCredentials.name ||
credentials?.pass !== apiDocumentationCredentials.pass
) {
return unauthorizedResponse();
}
next();
});
}
Upvotes: 0
Reputation: 117
For anyone wants authentication globally, u can add .addSecurityRequirements behind .build(). Don't worry login router will not effect, it depend on ur logic it require token or not.
const document = SwaggerModule.createDocument(
app,
new DocumentBuilder()
.setTitle('Book store')
.setDescription('The Book store API description')
.setVersion('1.0')
.addBearerAuth(
{
type: 'http',
scheme: 'Bearer',
bearerFormat: 'JWT',
in: 'header',
},
'token',
)
.addSecurityRequirements('token')
.build(),
);
// access http://localhost:${PORT}/docs
SwaggerModule.setup('docs', app, document);
app.use('/apidoc-json/', (req: Request, res: any) => res.send(document));
Upvotes: 6
Reputation: 3855
after adding .addBearerAuth()
to your swagger options, you should add @ApiBearerAuth()
to your Controller or it's methods.
NOTE: in order to keep token in swagger UI in browser after refreshing page you should set this in swagger options:
SwaggerModule.setup('docs', app, document, {
swaggerOptions: {
persistAuthorization: true, // this
},
});
Upvotes: 8
Reputation: 948
For anyone that uses the root as the swagger endpoint, you can do this:
import basicAuth from "express-basic-auth";
import { Request } from "express";
app.use(["/", "/-json"], function (req: Request, res, next) {
if (req.accepts().includes("application/json")) {
next();
} else {
const auth = basicAuth({
challenge: true,
users: {
[process.env.SWAGGER_USER]: process.env.SWAGGER_PASSWORD,
},
});
auth(req, res, next);
}
});
Upvotes: 0
Reputation: 19
In your main.ts file add this
const config = new DocumentBuilder()
.setTitle('App title')
.setDescription("Api description")
.setVersion('1.0')
.addTag('ApiTag')
.setContact('name', 'ulr', "email")
.addBearerAuth({ type: 'http', schema: 'Bearer', bearerFormat: 'Token' } as SecuritySchemeObject, 'Bearer')
.build();
Controller file
@ApiBearerAuth("Bearer")
@Controller('posts')
export class PostController {
constructor(private readonly postService: PostService) { }
}
Upvotes: 2
Reputation: 5051
based on previous answers, you may see this error (if you want to use express-basic-auth
module
Type 'typeof expressBasicAuth' has no call signatures.
Type originates at this import. A namespace-style import cannot be called or constructed, and will cause a failure at runtime. Consider using a default import or import require here instead
for this situation you can use .default
in main.ts
import * as basicAuth from 'express-basic-auth';
async function bootstrap() {
app.use(['/docs'], basicAuth.default({
challenge: true,
users: {
[process.env.SWAGGER_USERNAME]: process.env.SWAGGER_PASSWORD,
},
}));
const options = new DocumentBuilder()
.setTitle('api')
.setDescription('API description')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('docs', app, document);
}
Upvotes: 0
Reputation: 1837
For anyone who is not able to solve with the above answers.
Here is how I was able to add the bearer token
const options = new DocumentBuilder()
.setTitle('My API')
.setDescription('My api')
.setVersion('1.0.0')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
name: 'JWT',
description: 'Enter JWT token',
in: 'header',
},
'token'
)
.build();
Once you add this don't forget to add the decorator @ApiBearerAuth('token')
And one more thing to notice here is the second argument in the .addBearerAuth({...}, 'token')
method needs to add in the decorator then only you will be able to see the Authorization in the curl request.
@Controller('api')
@ApiBearerAuth('token')
You can keep it empty too @ApiBearerAuth()
and remove the second argument from the
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
name: 'JWT',
description: 'Enter JWT token',
in: 'header',
})
NestJS documentation needs to be improved
Upvotes: 0
Reputation: 2545
You can do that by adding addApiKey
or addBearerAuth
examples of which are described in other answers to this question.
From my side, I can add OAuth2 authentication There are some differences in implementation between @nestjs/swagger3** and @nestjs/swagger4**
For the @nestjs/swagger3**
const options = new DocumentBuilder()
.setTitle('API')
.setDescription('API')
.setVersion('1.0')
.setSchemes('https', 'http')
.addOAuth2('implicit', AUTH_URL, TOKEN_URL)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(swaggerPath, app, document, {
swaggerOptions: {
oauth2RedirectUrl: REDIRECT_URL, // after successfully logging
oauth: {
clientId: CLIENT_ID,
},
},
});
The addOAuth2 also supports flows as password, application and accessCode
For the @nestjs/swagger4**
const options = new DocumentBuilder()
.setTitle('API')
.setDescription('API description')
.setVersion(version)
.addServer(host)
.addOAuth2(
{
type: 'oauth2',
flows: {
implicit: {
authorizationUrl: AUTH_URL + `?nonce=${getRandomNumber(9)}`, // nonce parameter is required and can be random, for example nonce=123456789
tokenUrl: TOKEN_URL,
scopes: SCOPES, // { profile: 'profile' }
},
},
},
'Authentication'
)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(swaggerPath, app, document, {
swaggerOptions: {
oauth2RedirectUrl: REDIRECT_URL, // after successfully logging
oauth: {
clientId: CLIENT_ID,
},
},
});
Upvotes: 3
Reputation: 607
Just add .addBearerAuth()
(without any parameters) to your swagger options
and @ApiBearerAuth()
to your Controller methods
const options = new DocumentBuilder()
.setTitle('My App')
.setSchemes('https')
.setDescription('My App API documentation')
.setVersion('1.0')
.addBearerAuth()
.build()
Upvotes: 43
Reputation: 847
Updated following breaking/API changes in @nestjs/swagger version 4.0.
Hi, Took a lot of try&fail to get this right. The comments in the code is what is important to understand. The names rely on each other for this to work.
main.ts
const options = new DocumentBuilder()
.setTitle('my-title')
.setDescription('my-descirption')
.setVersion('1.0')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
name: 'JWT',
description: 'Enter JWT token',
in: 'header',
},
'JWT-auth', // This name here is important for matching up with @ApiBearerAuth() in your controller!
)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
And in your controller you do the following (note @ApiBearerAuth() using the same name as the name on the swagger options in main.ts):
app.controller.ts
@Roles(Role.Admin)
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiTags('Admin')
@ApiOperation({ summary: 'Get admin section' })
@Get('admin')
@ApiBearerAuth('JWT-auth') // This is the one that needs to match the name in main.ts
getAdminArea(@Request() req) {
return req.user;
}
Hope this saves somebody the time it took me to understand what was going on.
Upvotes: 39
Reputation: 19903
THIS IS FOR APIKEY NOT BEARER
In case somebody gets to this post and looking for apiKey
(instead of bearer) you need to follow this
in main.ts
const options = new DocumentBuilder()
.setTitle('CMOR')
.setDescription('CMOR API documentation')
.setVersion('1.0')
.addServer('/api')
.addApiKey({
type: 'apiKey', // this should be apiKey
name: 'api-key', // this is the name of the key you expect in header
in: 'header',
}, 'access-key' // this is the name to show and used in swagger
)
.build();
then in your controller or methods
@ApiTags('analyzer')
@ApiSecurity('access-key') // this is the name you set in Document builder
@Controller('analyzer')
export class ScreenAnalyzerController {
Upvotes: 8
Reputation: 1475
The following example is working very well
.addBearerAuth({ in: 'header', type: 'http' })
You should tell where is the token location in the in
prop
and since you override the default options you should pass the type
const options = new DocumentBuilder()
.setTitle('Api docs for mobile')
.setDescription('The api docs for the mobile application')
.setVersion('1.0')
.addBearerAuth({ in: 'header', type: 'http' })
.build();
the addBearerAuth
implementation
addBearerAuth(options = {
type: 'http'
}, name = 'bearer') {
this.addSecurity(name, Object.assign({ scheme: 'bearer', bearerFormat: 'JWT' }, options));
return this;
}
Upvotes: 5
Reputation: 1273
UPDATE
As per recent changes in DocumentBuilder
methods, this how it worked for me. Sharing for the people who are using new versions.
const options = new DocumentBuilder()
.setTitle('My API')
.setDescription('API used for testing purpose')
.setVersion('1.0.0')
.setBasePath('api')
.addBearerAuth(
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
'access-token',
)
.build();
const document = SwaggerModule.createDocument(app, options);
Update Also, please use @ApiBearerAuth()
on your controller function to add auth.
@Get('/test')
@ApiBearerAuth()
access-token
is the name for reference in swagger doc. Your token in the header will be passed as below:
curl -X GET "http://localhost:3004/test" -H "accept: application/json" -H "Authorization: Bearer test-token"
Upvotes: 25
Reputation: 49
For anyone with similar challenge, you can add Authentication to your Swagger UI in Nestjs as shown below.
const options = new DocumentBuilder()
.setTitle('Sample Project API')
.setDescription('This is a sample project to demonstrate auth in Swagger UI')
.setVersion('1.0')
.addTag('Nestjs Swagger UI')
.setContactEmail('[email protected]')
.addBearerAuth('Authorization', 'header', 'basic')
.setBasePath('api')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('docs', app, document);
So .addBearerAuth takes 3 arguments (key-name, location, authentication-type). authorization-type can be basic
, bearer
or apikey
Upvotes: 4