@@ -8,7 +8,7 @@ languages:
88 - typescript
99 - javascript
1010published_at : 2023-10-09
11- updated_at : 2024-05-15
11+ updated_at : 2024-12-11
1212---
1313
1414# 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
4242
4343<TabItem label = " TypeScript" >
4444
45- ``` typescript !! title:services/hello .ts
45+ ``` typescript title:services/api .ts
4646import { api } from ' @nitric/sdk'
4747
48- const helloApi = api (' main' )
48+ const mainApi = api (' main' )
4949
50- helloApi .get (' /hello/:name' , async (ctx ) => {
50+ mainApi .get (' /hello/:name' , async (ctx ) => {
5151 const { name } = ctx .req .params
52+
5253 ctx .res .body = ` Hello ${name } `
54+
5355 return ctx
5456})
5557```
@@ -58,14 +60,16 @@ helloApi.get('/hello/:name', async (ctx) => {
5860
5961<TabItem label = " JavaScript" >
6062
61- ``` javascript !! title:services/hello .js
63+ ``` javascript title:services/api .js
6264import { api } from ' @nitric/sdk'
6365
64- const helloApi = api (' main' )
66+ const mainApi = api (' main' )
6567
66- helloApi .get (' /hello/:name' , async (ctx ) => {
68+ mainApi .get (' /hello/:name' , async (ctx ) => {
6769 const { name } = ctx .req .params
70+
6871 ctx .res .body = ` Hello ${ name} `
72+
6973 return ctx
7074})
7175```
@@ -78,30 +82,32 @@ helloApi.get('/hello/:name', async (ctx) => {
7882
7983[ 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.
8084
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.
8286
8387<Tabs syncKey = " lang-node" >
8488
8589<TabItem label = " TypeScript" >
8690
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+ })
89100
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 ()],
100104})
101105
102- helloApi .get (' /hello/:name' , async (ctx ) => {
106+ mainApi .get (' /hello/:name' , async (ctx ) => {
103107 const { name } = ctx .req .params
108+
104109 ctx .res .body = ` Hello ${name } `
110+
105111 return ctx
106112})
107113```
@@ -110,24 +116,26 @@ helloApi.get('/hello/:name', async (ctx) => {
110116
111117<TabItem label = " JavaScript" >
112118
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'
115121
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>' ],
126127})
127128
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 ) => {
129135 const { name } = ctx .req .params
136+
130137 ctx .res .body = ` Hello ${ name} `
138+
131139 return ctx
132140})
133141```
@@ -141,74 +149,6 @@ helloApi.get('/hello/:name', async (ctx) => {
141149 values to match your values from Amazon Cognito.
142150</Note >
143151
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-
212152<Note >
213153 It's worth noting that these security definitions are * not* enforced when
214154 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
231171
232172<TabItem label = " TypeScript" >
233173
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'
236176import * as jwt from ' jsonwebtoken'
237177
238178interface AccessToken {
@@ -255,6 +195,8 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
255195 : ctx .req .headers [' authorization' ]
256196 const token = authHeader ?.split (' ' )[1 ]
257197
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.
258200 if (! token ) {
259201 ctx .res .status = 401
260202 ctx .res .body = ' Unauthorized'
@@ -264,8 +206,11 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
264206 // It's valuable to validate the token's shape, skipped here for brevity.
265207 ctx .user = jwt .decode (token ) as AccessToken
266208
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+ ) {
269214 ctx .res .status = 401
270215 ctx .res .body = ' Unauthorized'
271216 return ctx
@@ -274,24 +219,19 @@ const authorizeAuthors: HttpMiddleware = (ctx: AuthContext, next) => {
274219 return next (ctx )
275220}
276221
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 ()],
291231 middleware: [authorizeAuthors ],
292232})
293233
294- helloApi .get (' /hello/:name' , async (ctx : AuthContext ) => {
234+ mainApi .get (' /hello/:name' , async (ctx : AuthContext ) => {
295235 const { name } = ctx .req .params
296236 ctx .res .body = ` Hello ${name }, you're group memberships include: ${ctx .user [
297237 ' cognito:groups'
@@ -304,8 +244,8 @@ helloApi.get('/hello/:name', async (ctx: AuthContext) => {
304244
305245<TabItem label = " JavaScript" >
306246
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'
309249import * as jwt from ' jsonwebtoken'
310250
311251/**
@@ -320,17 +260,22 @@ const authorizeAuthors = (ctx, next) => {
320260 : ctx .req .headers [' authorization' ]
321261 const token = authHeader? .split (' ' )[1 ]
322262
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.
323265 if (! token) {
324266 ctx .res .status = 401
325267 ctx .res .body = ' Unauthorized'
326268 return ctx
327269 }
328270
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.
330272 ctx .user = jwt .decode (token)
331273
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+ ) {
334279 ctx .res .status = 401
335280 ctx .res .body = ' Unauthorized'
336281 return ctx
@@ -339,24 +284,19 @@ const authorizeAuthors = (ctx, next) => {
339284 return next (ctx)
340285}
341286
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.ap-southeast-2.amazonaws.com/ap-southeast-2_AOfSSbQeC/.well-known/openid-configuration' ,
291+ audiences: [' 10go0cgmme3mt71namegdvs2sd' ],
292+ })
293+
294+ const mainApi = api (' main' , {
295+ security: [defaultSecurityRule ()],
356296 middleware: [authorizeAuthors],
357297})
358298
359- helloApi .get (' /hello/:name' , async (ctx ) => {
299+ mainApi .get (' /hello/:name' , async (ctx ) => {
360300 const { name } = ctx .req .params
361301 ctx .res .body = ` Hello ${ name} , you're group memberships include: ${ ctx .user [
362302 ' cognito:groups'
0 commit comments