Skip to content

140 cognito#168

Open
chnnick wants to merge 39 commits into
mainfrom
140-Cognito
Open

140 cognito#168
chnnick wants to merge 39 commits into
mainfrom
140-Cognito

Conversation

@chnnick

@chnnick chnnick commented May 25, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Issue

Closes #140

📝 Description

Created an easily-importable NestJS guard that can be used by future projects to implement Cognito Authentication into their application. Amplify on the frontend authenticates users, providing registered users with a JWT access token that can be used to access authorized routes.

Briefly list the changes made to the code:

  1. NestJS guard that checks and verifies the Bearer token in the authorization header via AWS Cognito to determine access onto specific routes, puts the verified token payload under a user field of the request that can be read by other methods.
  2. CognitoService getUser() method that exposes the verified JWT payload attached to the request by the guard
  3. Cognito module that imported into App Module for authentication across entire application
  4. @public decorator that allows one to bypass authentication by putting metadata read by the guard
  5. Interface for an expected JWT payload used by the guard and service
  6. Cognito config that allows for a centralized place to extract env variables for Cognito
  7. Tests for the cognito service and cognito guard
  8. Implemented Amplify on the frontend to show an auth screen and configure auth for the application if frontend cognito env variables are set, if anyfrontend cognito env values are not set, there is no auth screen
  9. Cognito .env variables in example.env that is used by both the backend (Cognito) and frontend (Amplify)
  10. README.md in backend/aws/cognito for new devs/TLs that provides documentation for how the authentication flow works: Authentication in frontend -> Authorization in the backend, as well as how to expand in the future.

✔️ Verification

Backend TESTS:
Screenshot 2026-06-11 at 6 47 08 PM
Screenshot 2026-06-11 at 6 47 34 PM

Frontend TESTS:
No Auth:
Screenshot 2026-05-24 at 1 19 14 PM
With Auth:
Screenshot 2026-05-24 at 1 19 40 PM

🏕️ (Optional) Future Work / Notes

  • No styling on auth page, can import the @aws-amplify/ui-react/styles.css library on main.tsx to use their premade styling?

NEW: I think that a workshop on Authentication / Authorization would be helpful for future devs, would 100% be up to helping with that! I spent wayyy too much time figuring out the difference between the two and their uses in larger apps like which scaffolding will fork off of.

@chnnick chnnick requested a review from maxn990 as a code owner May 25, 2026 00:37
chnnick added 5 commits May 24, 2026 17:44
…e (COGNITO_USER_POOL_ID is set), but missing env variables for client_id and cognito_region both should not allow requests to go through
…into 140-Cognito

Note: added necessary packages for aws-amplify. Did not push merged changes that existed inside .nx

@dburkhart07 dburkhart07 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! Very educational for me to read through as well!

Comment thread apps/backend/src/aws/cognito/cognito.module.ts
Comment thread apps/backend/src/aws/cognito/cognito.config.ts Outdated
Comment thread apps/backend/src/aws/cognito/cognito.module.ts Outdated
Comment thread apps/backend/src/aws/cognito/cognito.service.spec.ts Outdated
Comment thread apps/backend/src/aws/cognito/cognito.guard.ts Outdated
Comment thread apps/backend/src/aws/cognito/cognito.guard.ts Outdated
Comment thread apps/backend/src/aws/cognito/README.md Outdated
Comment thread apps/frontend/src/auth/auth.config.ts
Comment thread apps/frontend/src/auth/auth.config.ts Outdated
Comment thread example.env Outdated
@chnnick

chnnick commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Fell down an authentication/authorization rabbit hole atm will update with new changes soon for a better approach for an authentication-only module that still allows authorization (RBAC) later down the line

@maxn990 maxn990 requested a review from dburkhart07 June 16, 2026 13:36

@dburkhart07 dburkhart07 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few small things

return;
}

const poolId: string = userPoolId;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we defined these constants at the top of the file, i dont really think we need to define them again here


### Auth model

- **Verification** — `CognitoJWTGuard` is the only component that validates JWTs (signature, issuer, audience).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CognitoJwtGuard does not validate the audience the way we have it implemented here

4. **Frontend calls the backend with the access token.** The client attaches it on every request as a header: `Authorization: Bearer <access_token>`.

5. **The Guard checks the token.** `CognitoJWTGuard` runs on every route (it's registered as a global `APP_GUARD`). For each request it:
- lets the request through immediately if auth is disabled (Cognito env vars unset or the route is marked `@Public()`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes it seem like Public disables auth. I think you should specify here that specifying a route as public does not disable anything, but rather just makes an easy bypass

Comment thread example.env
# Note: Leaving any field unset disables auth ENTIRELY
COGNITO_USER_POOL_ID=us-east-2_AbCdEf123
COGNITO_CLIENT_ID=4h57k9lmno1pqrstuv2wxyz3ab
COGNITO_REGION=us-east-2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we really shouldnt specify a specific COGNITO_REGION. This should just be one single AWS_REGION variable the project uses all AWS things for

}
const normalized = value.trim().toLowerCase();
return (
normalized !== '' && normalized !== 'null' && normalized !== 'undefined'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalized will never be null or undefined here.

const request = context.switchToHttp().getRequest<Request>();
const token = extractBearerToken(request);
if (!token) {
throw new UnauthorizedException();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give this exception (and the ones below) specific exception messages as well?

*/
async canActivate(context: ExecutionContext): Promise<boolean> {
// If authentication is not enabled, allow the request to proceed
if (!isAuthEnabled()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some form of log here so that the user knows they are bypassing it perhaps?

private verifyToken(token: string): Promise<AccessTokenPayload> {
// If the region, user pool ID, or client ID is not set, throw an unauthorized exception by default
const config = getCognitoConfig();
if (!config) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will never be hit (if we got to verifyToken we already know auth is enabled)

getUser(request: Request): AccessTokenPayload | null {
// If authentication is not enabled, return null
if (!isAuthEnabled()) {
return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we maybe add some logs into these return nulls (and if there is anywhere else this is happening in the module)? that way the user knows whether the issue is cognito being down, or their user actually being unreachable.

return (
typeof payload.sub === 'string' &&
typeof payload.iss === 'string' &&
typeof payload.token_use === 'string' &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we not check that this is access?

typeof payload.iss === 'string' &&
typeof payload.token_use === 'string' &&
typeof payload.exp === 'number' &&
typeof payload.iat === 'number'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should check that this is in the past

* @param value - The value to check, typically a decoded JWT payload.
* @returns `true` if `value` matches the {@link AccessTokenPayload} shape.
*/
export function isAccessTokenPayload(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have special checks for the client_id? what about cognito_groups

client_id?: string; // Client ID used during authentication (ex: <your client ID>)
exp: number; // Expiration time (Unix timestamp)
iat: number; // Issued at time (Unix timestamp)
email?: string; // Email (ex: test@example.com)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do we need this field for? i dont think access tokens normally send email addresses through that. i think the verify in jwt.strategy doesnt even need the email.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cognito Module

2 participants