@@ -8,7 +8,7 @@ languages:
8
8
- typescript
9
9
- javascript
10
10
published_at : 2023-10-09
11
- updated_at : 2024-05-15
11
+ updated_at : 2024-12-11
12
12
---
13
13
14
14
# Securing APIs with Amazon Cognito
@@ -42,14 +42,16 @@ In this new Nitric project there will be an example API with a single `GET: /hel
42
42
43
43
<TabItem label = " TypeScript" >
44
44
45
- ``` typescript !! title:services/hello .ts
45
+ ``` typescript title:services/api .ts
46
46
import { api } from ' @nitric/sdk'
47
47
48
- const helloApi = api (' main' )
48
+ const mainApi = api (' main' )
49
49
50
- helloApi .get (' /hello/:name' , async (ctx ) => {
50
+ mainApi .get (' /hello/:name' , async (ctx ) => {
51
51
const { name } = ctx .req .params
52
+
52
53
ctx .res .body = ` Hello ${name } `
54
+
53
55
return ctx
54
56
})
55
57
```
@@ -58,14 +60,16 @@ helloApi.get('/hello/:name', async (ctx) => {
58
60
59
61
<TabItem label = " JavaScript" >
60
62
61
- ``` javascript !! title:services/hello .js
63
+ ``` javascript title:services/api .js
62
64
import { api } from ' @nitric/sdk'
63
65
64
- const helloApi = api (' main' )
66
+ const mainApi = api (' main' )
65
67
66
- helloApi .get (' /hello/:name' , async (ctx ) => {
68
+ mainApi .get (' /hello/:name' , async (ctx ) => {
67
69
const { name } = ctx .req .params
70
+
68
71
ctx .res .body = ` Hello ${ name} `
72
+
69
73
return ctx
70
74
})
71
75
```
@@ -78,30 +82,32 @@ helloApi.get('/hello/:name', async (ctx) => {
78
82
79
83
[ Nitric APIs] ( /apis#api-security ) allow initial token authentication to be performed by the API Gateways of various cloud providers, such as AWS API Gateway. When configured correctly this will ensure unauthenticated requests are rejected before reaching your application code.
80
84
81
- To add this API Gateway authentication we need to create a ` security definition ` and then apply that definition to specific routes or the entire API. Here we'll update the ` main ` API by adding a new security definition named ` cognito ` .
85
+ To add this API Gateway authentication we need to create a ` security definition ` and then apply that definition to specific routes or the entire API. Here we'll update the ` main ` API by adding a new security definition named ` cognito ` . This will apply to all routes in the API.
82
86
83
87
<Tabs syncKey = " lang-node" >
84
88
85
89
<TabItem label = " TypeScript" >
86
90
87
- ``` typescript !! title:services/hello.ts
88
- import { api } from ' @nitric/sdk'
91
+ ``` typescript title:services/api.ts
92
+ import { api , oidcRule } from ' @nitric/sdk'
93
+
94
+ const defaultSecurityRule = oidcRule ({
95
+ name: ' cognito' ,
96
+ issuer:
97
+ ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
98
+ audiences: [' <app-client-id>' ],
99
+ })
89
100
90
- const helloApi = api (' main' , {
91
- // declare security definition named 'cognito'.
92
- securityDefinitions: {
93
- cognito: {
94
- kind: ' jwt' ,
95
- issuer:
96
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
97
- audiences: [' <app-client-id>' ],
98
- },
99
- },
101
+ const mainApi = api (' main' , {
102
+ // apply the security definition to all routes in this API.
103
+ security: [defaultSecurityRule ()],
100
104
})
101
105
102
- helloApi .get (' /hello/:name' , async (ctx ) => {
106
+ mainApi .get (' /hello/:name' , async (ctx ) => {
103
107
const { name } = ctx .req .params
108
+
104
109
ctx .res .body = ` Hello ${name } `
110
+
105
111
return ctx
106
112
})
107
113
```
@@ -110,24 +116,26 @@ helloApi.get('/hello/:name', async (ctx) => {
110
116
111
117
<TabItem label = " JavaScript" >
112
118
113
- ``` javascript !! title:services/hello .js
114
- import { api } from ' @nitric/sdk'
119
+ ``` javascript title:services/api .js
120
+ import { api , oidcRule } from ' @nitric/sdk'
115
121
116
- const helloApi = api (' main' , {
117
- // declare security definition named 'cognito'.
118
- securityDefinitions: {
119
- cognito: {
120
- kind: ' jwt' ,
121
- issuer:
122
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
123
- audiences: [' <app-client-id>' ],
124
- },
125
- },
122
+ const defaultSecurityRule = oidcRule ({
123
+ name: ' cognito' ,
124
+ issuer:
125
+ ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
126
+ audiences: [' <app-client-id>' ],
126
127
})
127
128
128
- helloApi .get (' /hello/:name' , async (ctx ) => {
129
+ const mainApi = api (' main' , {
130
+ // apply the security definition to all routes in this API.
131
+ security: [defaultSecurityRule ()],
132
+ })
133
+
134
+ mainApi .get (' /hello/:name' , async (ctx ) => {
129
135
const { name } = ctx .req .params
136
+
130
137
ctx .res .body = ` Hello ${ name} `
138
+
131
139
return ctx
132
140
})
133
141
```
@@ -141,74 +149,6 @@ helloApi.get('/hello/:name', async (ctx) => {
141
149
values to match your values from Amazon Cognito.
142
150
</Note >
143
151
144
- Next, we need to ensure this security definition is applied, otherwise it won't be enforced. APIs can have multiple security definitions applied at different levels or to different routes. For example, you might have one security definition for external users and another for administrators/internal users.
145
-
146
- In this example we'll apply the security at the API level, ensuring all routes require authentication.
147
-
148
- <Tabs syncKey = " lang-node" >
149
-
150
- <TabItem label = " TypeScript" >
151
-
152
- ``` typescript !! title:services/hello.ts
153
- import { api } from ' @nitric/sdk'
154
-
155
- const helloApi = api (' main' , {
156
- // declare security definition named 'cognito'.
157
- securityDefinitions: {
158
- cognito: {
159
- kind: ' jwt' ,
160
- issuer:
161
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
162
- audiences: [' <app-client-id>' ],
163
- },
164
- },
165
- // apply the 'cognito' security definition to all routes in this API.
166
- security: {
167
- cognito: [],
168
- },
169
- })
170
-
171
- helloApi .get (' /hello/:name' , async (ctx ) => {
172
- const { name } = ctx .req .params
173
- ctx .res .body = ` Hello ${name } `
174
- return ctx
175
- })
176
- ```
177
-
178
- </TabItem >
179
-
180
- <TabItem label = " JavaScript" >
181
-
182
- ``` javascript !! title:services/hello.js
183
- import { api } from ' @nitric/sdk'
184
-
185
- const helloApi = api (' main' , {
186
- // declare security definition named 'cognito'.
187
- securityDefinitions: {
188
- cognito: {
189
- kind: ' jwt' ,
190
- issuer:
191
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
192
- audiences: [' <app-client-id>' ],
193
- },
194
- },
195
- // apply the 'cognito' security definition to all routes in this API.
196
- security: {
197
- cognito: [],
198
- },
199
- })
200
-
201
- helloApi .get (' /hello/:name' , async (ctx ) => {
202
- const { name } = ctx .req .params
203
- ctx .res .body = ` Hello ${ name} `
204
- return ctx
205
- })
206
- ```
207
-
208
- </TabItem >
209
-
210
- </Tabs >
211
-
212
152
<Note >
213
153
It's worth noting that these security definitions are * not* enforced when
214
154
testing your Nitric services locally. Currently, they're only enforced when
@@ -231,8 +171,8 @@ In the example below we're simply checking whether the user is a member of a par
231
171
232
172
<TabItem label = " TypeScript" >
233
173
234
- ``` typescript !! title:services/hello .ts
235
- import { api , HttpContext , HttpMiddleware } from ' @nitric/sdk'
174
+ ``` typescript title:services/api .ts
175
+ import { api , HttpContext , HttpMiddleware , oidcRule } from ' @nitric/sdk'
236
176
import * as jwt from ' jsonwebtoken'
237
177
238
178
interface AccessToken {
@@ -255,6 +195,8 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
255
195
: ctx .req .headers [' authorization' ]
256
196
const token = authHeader ?.split (' ' )[1 ]
257
197
198
+ // If no token is present, deny access.
199
+ // There should be a token as the security rules would be checked first, but this is a good practice.
258
200
if (! token ) {
259
201
ctx .res .status = 401
260
202
ctx .res .body = ' Unauthorized'
@@ -264,8 +206,11 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
264
206
// It's valuable to validate the token's shape, skipped here for brevity.
265
207
ctx .user = jwt .decode (token ) as AccessToken
266
208
267
- // Ensure the user is a member of the "authors" cognito group
268
- if (! ctx .user [' cognito:groups' ].includes (' authors' )) {
209
+ // If the user is not a member of any groups or not an "author", deny access
210
+ if (
211
+ ! ctx .user [' cognito:groups' ] ||
212
+ ! ctx .user [' cognito:groups' ].includes (' authors' )
213
+ ) {
269
214
ctx .res .status = 401
270
215
ctx .res .body = ' Unauthorized'
271
216
return ctx
@@ -274,24 +219,19 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
274
219
return next (ctx )
275
220
}
276
221
277
- const helloApi = api (' main' , {
278
- // declare security definition named 'cognito'.
279
- securityDefinitions: {
280
- cognito: {
281
- kind: ' jwt' ,
282
- issuer:
283
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
284
- audiences: [' <app-client-id>' ],
285
- },
286
- },
287
- // apply the 'cognito' security definition to all routes in this API.
288
- security: {
289
- cognito: [],
290
- },
222
+ const defaultSecurityRule = oidcRule ({
223
+ name: ' cognito' ,
224
+ issuer:
225
+ ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
226
+ audiences: [' <app-client-id>' ],
227
+ })
228
+
229
+ const mainApi = api (' main' , {
230
+ security: [defaultSecurityRule ()],
291
231
middleware: [authorizeAuthors ],
292
232
})
293
233
294
- helloApi .get (' /hello/:name' , async (ctx : AuthContext ) => {
234
+ mainApi .get (' /hello/:name' , async (ctx : AuthContext ) => {
295
235
const { name } = ctx .req .params
296
236
ctx .res .body = ` Hello ${name }, you're group memberships include: ${ctx .user [
297
237
' cognito:groups'
@@ -304,8 +244,8 @@ helloApi.get('/hello/:name', async (ctx: AuthContext) => {
304
244
305
245
<TabItem label = " JavaScript" >
306
246
307
- ``` javascript !! title:services/hello .js
308
- import { api , HttpContext } from ' @nitric/sdk'
247
+ ``` javascript title:services/api .js
248
+ import { api , oidcRule } from ' @nitric/sdk'
309
249
import * as jwt from ' jsonwebtoken'
310
250
311
251
/**
@@ -320,17 +260,22 @@ const authorizeAuthors = (ctx, next) => {
320
260
: ctx .req .headers [' authorization' ]
321
261
const token = authHeader? .split (' ' )[1 ]
322
262
263
+ // If no token is present, deny access.
264
+ // There should be a token as the security rules would be checked first, but this is a good practice.
323
265
if (! token) {
324
266
ctx .res .status = 401
325
267
ctx .res .body = ' Unauthorized'
326
268
return ctx
327
269
}
328
270
329
- // It's valuable to check the token's shape, skipped here for brevity.
271
+ // It's valuable to validate the token's shape, skipped here for brevity.
330
272
ctx .user = jwt .decode (token)
331
273
332
- // Ensure the user is a member of the "authors" cognito group
333
- if (! ctx .user [' cognito:groups' ].includes (' authors' )) {
274
+ // If the user is not a member of any groups or not an "author", deny access
275
+ if (
276
+ ! ctx .user [' cognito:groups' ] ||
277
+ ! ctx .user [' cognito:groups' ].includes (' authors' )
278
+ ) {
334
279
ctx .res .status = 401
335
280
ctx .res .body = ' Unauthorized'
336
281
return ctx
@@ -339,24 +284,19 @@ const authorizeAuthors = (ctx, next) => {
339
284
return next (ctx)
340
285
}
341
286
342
- const helloApi = api (' main' , {
343
- // declare security definition named 'cognito'.
344
- securityDefinitions: {
345
- cognito: {
346
- kind: ' jwt' ,
347
- issuer:
348
- ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
349
- audiences: [' <app-client-id>' ],
350
- },
351
- },
352
- // apply the 'cognito' security definition to all routes in this API.
353
- security: {
354
- cognito: [],
355
- },
287
+ const defaultSecurityRule = oidcRule ({
288
+ name: ' cognito' ,
289
+ issuer:
290
+ ' https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/openid-configuration' ,
291
+ audiences: [' <app-client-id>' ],
292
+ })
293
+
294
+ const mainApi = api (' main' , {
295
+ security: [defaultSecurityRule ()],
356
296
middleware: [authorizeAuthors],
357
297
})
358
298
359
- helloApi .get (' /hello/:name' , async (ctx ) => {
299
+ mainApi .get (' /hello/:name' , async (ctx ) => {
360
300
const { name } = ctx .req .params
361
301
ctx .res .body = ` Hello ${ name} , you're group memberships include: ${ ctx .user [
362
302
' cognito:groups'
0 commit comments