Security schemes
Tell callers how to authenticate: declare the staff's bearer JWT and the partners' API key as security schemes, mark each operation with the scheme it needs, document the sign-in exchange, and declare the 401 every operation can answer.
What you'll learn
- Explain what a security scheme is in OpenAPI (a named way to authenticate, declared once) and what a security requirement on an operation says
- Declare the built-in schemes with addBearerAuth, addBasicAuth, addCookieAuth and addOAuth2, and any other with addSecurity
- Mark an operation with @ApiBearerAuth() or @ApiSecurity(name), so the Swagger UI's Authorize button sends the credential and generated clients know to
- Document the sign-in exchange itself: the credentials as a DTO with examples, the token as a response model, 401 for the wrong password
The Security course locked the cats API: staff sign in for a JWT, the guard bound with APP_GUARD refuses everything else, and now a partner integration gets a shared key for one statistics route. None of that is in the document. A reader sees POST /cats, tries it from the Swagger UI, gets a 401 and has no idea what to send. A generated client has no notion that some calls need a header. OpenAPI has a place for this: security schemes, declared once for the document, and security requirements, which say per operation which scheme it needs. Swagger UI turns them into an Authorize button that then sends the credential with every request that requires it.
Schemes, once
A scheme is a named way to authenticate: HTTP basic or bearer, an API key in a header, a query or a cookie, OAuth2 with its flows. The DocumentBuilder declares them under components.securitySchemes. The common ones have a method each:
new DocumentBuilder()
.addBearerAuth() // "bearer": http, bearer, JWT
.addBasicAuth() // "basic": http, basic
.addCookieAuth('connect.sid') // "cookie": apiKey in a cookie
.addOAuth2() // "oauth2"
Each takes options (the scheme object, and a second argument to change the name). Anything else is addSecurity(name, scheme) with the raw scheme object, the docs' example being a hand-declared basic scheme:
.addSecurity('partner', { type: 'apiKey', in: 'query', name: 'key' })
Declaring a scheme says nothing about which operations use it. That is the second half.
Requirements, per operation
@ApiBearerAuth() on a method or a controller adds security: [{ bearer: [] }] to those operations; @ApiBasicAuth(), @ApiCookieAuth() and @ApiOAuth2(['scope']) do the same for their schemes, and @ApiSecurity('partner') for one you named. Put it where the guard is: on the controller when every route needs a token, on the methods when some are public. A route with no requirement is documented as open, which is a claim you should only make about routes the guard really lets through.
The document describes here as everywhere. @ApiBearerAuth() does not protect a route; the guard does. A route with the decorator and no guard is open with a lock icon on it, and a route with a guard and no decorator is a mystery 401. Keep the two together, and let the Security course's @Public() decorator mark the exceptions in both senses.
The exchange itself
A bearer token comes from somewhere, and that route deserves the most care in the document because it is the first one a reader calls. The credentials DTO wants @ApiProperty({ example }) on each field so the Swagger UI prefills something that works; the successful response wants a type (a small class with the token property, so the reader knows the key to read); the failure wants @ApiUnauthorizedResponse({ description }) saying it was the password, since that is the one 401 that is not about a missing token. A 401 that every protected route shares goes in addGlobalResponse, as lesson 3's 500 did.
Your task
The API has the JWT guard from the Security course, @Public() on the reads and on POST /auth/login, and a new ApiKeyGuard on GET /cats/stats that wants an x-shelter-key header. The document knows none of this.
- Declare two schemes: the built-in bearer scheme, and
shelter-key, an API key sent in thex-shelter-keyheader. Declare, for every operation, that a 401 meansThe token or key is missing, expired or wrong. - Mark the create, the delete and the profile as needing the bearer token, and the stats as needing the partner key.
- Document the sign-in:
usernameandpasswordwith the examplesjohnandchangeme, a 200 whose body is anAccessTokenDtodescribed asThe token; send it as Authorization: Bearer <token>, and a 401Wrong username or password.
Then open the app in a new tab, press Authorize, paste a token from POST /auth/login, and watch the create succeed from the page.
When it fails
components.securitySchemesis missing, though the operations havesecurity: [{ bearer: [] }]: the decorators are there andaddBearerAuth()is not. Swagger UI shows no Authorize button, because it has no scheme to ask for.- The stats operation says
security: [{ "shelter-key": [] }]but the scheme is namedshelterKey: the name in@ApiSecurity()must match the one given toaddSecurity()exactly; the document does not check. POST /catsanswers 401 with the token pasted into Authorize: the scheme was declared asapiKeyinstead of withaddBearerAuth(), so the UI sent the token in a header of the wrong name. The guard wantsAuthorization: Bearer <token>./auth/loginshows aSignInDtowith no properties: the class has none of the@ApiProperty()decorators; lesson 1's rule applies to credentials too.
Remember
- Schemes are declared once in the builder (
addBearerAuth,addBasicAuth,addCookieAuth,addOAuth2, oraddSecurityfor your own); requirements go on operations (@ApiBearerAuth,@ApiSecurity(name)). - The name in the decorator must match the name of the scheme.
- The decorator documents; the guard enforces. Put them in the same places.
- Document the sign-in route best of all: examples in, a token model out, and the one 401 that means the password.
Stuck? Show a hint
main.ts: .addBearerAuth(), .addSecurity('shelter-key', { type: 'apiKey', in: 'header', name: 'x-shelter-key' }), .addGlobalResponse({ status: 401, description: '...' }). sign-in.dto.ts: @ApiProperty({ example: 'john' }) and @ApiProperty({ example: 'changeme' }); access-token.dto.ts: @ApiProperty(). auth.controller.ts: @ApiOkResponse({ description, type: AccessTokenDto }) and @ApiUnauthorizedResponse({ description }) on signIn, @ApiBearerAuth() on getProfile. cats.controller.ts: @ApiBearerAuth() on create and remove, @ApiSecurity('shelter-key') on stats.