josec89
josec89

Reputation: 2172

Is it possible to add Authentication to access to NestJS' Swagger Explorer

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

Answers (15)

KiwiKilian
KiwiKilian

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

Nelson Bwogora
Nelson Bwogora

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

Ph&#225;t Đỗ
Ph&#225;t Đỗ

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

Ali Sherafat
Ali Sherafat

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

acw
acw

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

user13183485
user13183485

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) { }
}

Set your token

Upvotes: 2

Mohammad Yaser Ahmadi
Mohammad Yaser Ahmadi

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

iamsujit
iamsujit

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

CyberEternal
CyberEternal

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

linuxistsuper
linuxistsuper

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

Lior Kupers
Lior Kupers

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

Reza
Reza

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

Ali Turki
Ali Turki

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

pravindot17
pravindot17

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

Bilyamin Adam
Bilyamin Adam

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

Related Questions