140 cognito#168
Conversation
…added required env keys to example.env, configured amplify in separate auth file only if both environment variables exist/are set.
…igure runs correctly with cognito values as strings.
…array of strings or a single string.
…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
…into 140-Cognito Note: added necessary packages for aws-amplify. Did not push merged changes that existed inside .nx
…into 140-Cognito
dburkhart07
left a comment
There was a problem hiding this comment.
Amazing! Very educational for me to read through as well!
…or greater accessiblity
… of never type for mock requests
… any env variables)
…autogenerated in vite.config, also set the project root to allow frontend access to root /.env file.
…into 140-Cognito
|
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 |
CognitoJWTPayload to just AccessTokenPayload for accuracy, javadoc comments
…into 140-Cognito
| # 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 |
There was a problem hiding this comment.
we really shouldnt specify a specific COGNITO_REGION. This should just be one single AWS_REGION variable the project uses all AWS things for
There was a problem hiding this comment.
should I just leave the other fields empty as well?? I'll update the comment to say "empty or missing" instead of "unset" as well
| const request = context.switchToHttp().getRequest<Request>(); | ||
| const token = extractBearerToken(request); | ||
| if (!token) { | ||
| throw new UnauthorizedException(); |
There was a problem hiding this comment.
Can we give this exception (and the ones below) specific exception messages as well?
There was a problem hiding this comment.
I added a specific message to this one specific exception regarding a missing bearer token in a commit, but imo i think it'd be better to leave the exceptions that are thrown when validating the jwt to be general unauthorized exceptions that dont reveal internal information.
There was a problem hiding this comment.
Added logging for the exception information instead ^
| */ | ||
| async canActivate(context: ExecutionContext): Promise<boolean> { | ||
| // If authentication is not enabled, allow the request to proceed | ||
| if (!isAuthEnabled()) { |
There was a problem hiding this comment.
Can we add some form of log here so that the user knows they are bypassing it perhaps?
There was a problem hiding this comment.
I can add server side logging, but I think its unecessary to add client-visible logging to tell the user they are bypassing authentication? Might be misunderstanding this request
| getUser(request: Request): AccessTokenPayload | null { | ||
| // If authentication is not enabled, return null | ||
| if (!isAuthEnabled()) { | ||
| return null; |
There was a problem hiding this comment.
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.
| typeof payload.iss === 'string' && | ||
| typeof payload.token_use === 'string' && | ||
| typeof payload.exp === 'number' && | ||
| typeof payload.iat === 'number' |
There was a problem hiding this comment.
should check that this is in the past
There was a problem hiding this comment.
hmm, not sure what you mean by the past? jwt.verify will check for expired tokens though and return err on the guard.
logging for debugging Cognito errors, added exception message for missing bearer token
dburkhart07
left a comment
There was a problem hiding this comment.
hopefully some last comments. c4c security is becoming so good!!!!!!
| } | ||
|
|
||
| // Checks if the value is a non-empty string | ||
| function isNonEmptyEnv(value: string | undefined): value is string { |
There was a problem hiding this comment.
wonder if we could convert this to a utils function in a separate utils folder. seems like it could be useful elsewhere too.
| } | ||
|
|
||
| // Check if the cognito information is present in the environment variables | ||
| export const cognitoInformationPresent = |
There was a problem hiding this comment.
can we just make this a function just like the backend, and call that the same way?
| return null; | ||
| } | ||
|
|
||
| return { |
There was a problem hiding this comment.
im currently getting an error with these strings potentially being undefined. we should just do a regular check for all 3 variables of !isNonEmptyEnv
| isGlobal: true, | ||
| }), | ||
| TypeOrmModule.forRoot(AppDataSource.options), | ||
| CognitoModule, |
There was a problem hiding this comment.
not quite sure how you would do it. but we should add a specific check that, if we have some of the env variables set (e.g. pool id and region, but not client id), we should be throwing an error on startup, rather than making everything public. probably a good security practice
| } | ||
|
|
||
| // Checks if the value is a non-empty string | ||
| function isNonEmptyEnv(value: string | undefined): value is string { |
There was a problem hiding this comment.
we should make this function the exact same thing as the frontend as well, so we can reject null entries and dont have to worry at all about the frontend and backend disagreeing about whether auth is enabled or not.
| * @param value - The value to check, typically a decoded JWT payload. | ||
| * @returns `true` if `value` matches the {@link AccessTokenPayload} shape. | ||
| */ | ||
| export function isAccessTokenPayload( |
There was a problem hiding this comment.
can we move this all to the cognito guard? this isnt really a defined type, which is a bit misleading.
| @@ -0,0 +1,40 @@ | |||
| // Checks if the authentication is enabled | |||
| export function isAuthEnabled(): boolean { | |||
There was a problem hiding this comment.
the way we have this setup right now, we will run into issues with strict null checks, this file wont compile. we should make isAuthEnabled simply check that getCognitoConfig !== null
|
|
||
| # AWS Cognito | ||
| # Note: Leaving any field empty or missing disables auth ENTIRELY | ||
| COGNITO_USER_POOL_ID= |
There was a problem hiding this comment.
should add something here to make it clear to never put a secret in one of the VITE env variables, since these are client-facing. Setting one of these to a secret value means it gets exposed to anyone using the site. just a good cautionary measure to take.
| import { APP_GUARD } from '@nestjs/core'; | ||
| import { CognitoService } from './cognito.service'; | ||
|
|
||
| @Global() |
There was a problem hiding this comment.
i think, for modules that rely on env variables to function entirely (so all of our aws modules), we should be validating the env variables at compile time. can we change this to rather use something like this:
export class CognitoModule implements OnModuleInit {
private readonly logger = new Logger(CognitoModule.name);
onModuleInit() {
const config = isAuthEnabled();
if (config === null) {
this.logger.warn('Cognito auth disabled: no env vars set. All routes open.');
} else {
this.logger.log(`Cognito auth enabled for pool ${config.userPoolId}`);
}
}
}
while you are at it, would you mind making this change for the s3 and ses, where we would check the specific env variables that we need to check for ses being enabled, and s3 being enabled?
ℹ️ 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:
✔️ Verification
Backend TESTS:


Frontend TESTS:


No Auth:
With Auth:
🏕️ (Optional) Future Work / Notes
@aws-amplify/ui-react/styles.csslibrary 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.