Files
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
Security schemes
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.
Press Run tests to start the app. Its log appears here.Graded endpoints
addBearerAuth declares the built-in bearer scheme (an http scheme carrying a JWT); addSecurity declares the shelter-key scheme by hand, an API key read from a header
@ApiBearerAuth puts a bearer requirement on the create, the delete and the profile; @ApiSecurity('shelter-key') on the stats; the global 401 is on every operation
The credentials DTO has examples a reader can try, the 200 references the token model, and the 401 says what went wrong
What lesson 3 documented is still there: summaries, the Cat model on the list and the fetch, the create's 201, the delete's 204 and 404
No security requirement in the document, and no token needed: @Public() lets the guard through
The bearer requirement the document declares is enforced by the guard: 401 before the handler runs
The stats route is public to the JWT guard but ApiKeyGuard wants the header; the message says which
The x-shelter-key header the shelter-key scheme describes
The exchange the document describes: credentials in, a JWT out (its first segment is the same base64url header for every HS256 token). The token is kept for the requests below
The 401 the login documents
Authorization: Bearer <token>, exactly as the bearer scheme says
The guard put the token's payload on the request
A staff token is not a partner key: the operation's requirement is shelter-key, and only that header satisfies it
A public read, as the document's missing security requirement says
The delete's bearer requirement, enforced by the guard
204 with no body, as documented
The cat is no longer there