Skip to content

Conversation

@dburkhart07
Copy link

@dburkhart07 dburkhart07 commented Apr 1, 2025

ℹ️ Issue

Resolves Implement Role-Based Authentication (BACKEND)

Closes Implement Role Based Authentication (Backend)
NOTE: Please merge this PR

📝 Description

I implemented the role based authentication for the backend. This consisted of setting up the jwt strategy to be properly configured, as well as creating a role guard and role decorator. After this, I added the AuthGuard('jwt') to every backend controller, making it so nothing in the backend is accessible unless the user signs in. I then used the role decorator and guard, as well as the Role enum already defined to specify which roles could access which api endpoints (I just did this for the pantries and requests, since I am not sure who we want accessing the api endpoint outside of these). To avoid unauthorized users accessing pages via the frontend, I implemented a check that would redirect the user to an error page if, while making a backend call with the axios interceptor in the api client, it received a 403 (unauthorized user) error.

Briefly list the changes made to the code:

  1. Added jwt authentication to all api backend controllers to ensure user is sigend in
  2. Added role decorators and guards to specify which roles could access which endpoints
  3. Created an axios interceptor in the frontend to redirect users to an error page if they are unauthorized
  4. Refactored api logic in request form to put the actual request within apiClient.ts as opposed to having an axios call in the file itself

Explanation of some key features
Changes to modules: I adjusted a majority of the modules files to include the AuthModule on import. Since the AuthModule exports and provides both the AuthService and JwtStrategy, this allows us to use both them and their providers in all of our modules.

JwtStrategy file: This file serves as the purpose of validating jwts we send over when logging in through AWS Cognito. This file tells the Passport how to validate JWTs signed by Cognito on start up. We then also validate the payload we receive from the jwt, extracting the proper database user as such (NOTE: The actual ability to register a user is not implemented yet). You can see from the logs if you run it, our users in Cognito all have a role as VOLUNTEER, but we use their email to get their real info from the postgres database.

Majority of the auth files: Currently, we are not using any of the auth endpoints, and almost none of the authService functions. We should see how these end up being useful later on, but, since they were included with scaffolding and we are already using AWS Cognito, we may be able to delete a lot of the auth code.

CurrentUserInterceptor: We ended up not using this anywhere currently, as AWS Cognito gets the database user for us in the validate function within the JwtStrategy. Perhaps we will use it later, but for now it has been removed from all of our controllers.

ApiClient and frontend: We need to attach an access token to all of the api requests into the backend so that we can validate the user is who they say they are when retrieving their information. For this, we added a hooks folder and a way to just fetch the session info after they have logged in. We use the idToken to verify this information, so we attach that onto the apiClient.accessToken, which is applied to every request we make (this enforces the user to be logged in to use any of our endpoints. We also added special response error handling from the api client too. When we receive a 403 error (which is special unauthorized error code), we know the user did not have a valid role, so we redirect them. There could be a better way to handle this down the road, but for now this is what I thought of.

Process of registering an endpoint for role-based authentication:

  1. Make sure the specific controller is using the @UseGuards(AuthGuard('jwt'), RolesGuard). The AuthGuard('jwt') allows us to get actual user information in our context, and the RolesGuard allows us to take the actual @roles decorator, and apply logic to check if the user's role is permitted
  2. We can assign roles to specific endpoints, as seen in the pantries.controller.ts file
  3. Additionlly, you will want to make sure the controller you are using has access to the AuthModule, as this provides access to the JwtStrategy and AuthService, whch are both needed. See how the PantriesModule has imported it for futher testing or development.

✔️ Verification

To verify, I registered a user with an email and password on AWS Cognito. I then manually created this entry in the Postgres table (this will have to be changed later to allow for users to be automatically registered), using the same email, and assigning them a role. I gave the user a PANTRY role, and attempted to access an api endpoint that was only accessible to pantries. I then changed it to ADMIN, and tried accessing it again, to make sure that it redirected me to the error page.

Provide screenshots of any new components, styling changes, or pages.
Accessing food request form as a pantry
image
image

Accessing food request form as an admin
image
image

🏕️ (Optional) Future Work / Notes

This is just the basic implementation for the backend. As we figure out which users are able to access which endpoints, we will need to simply just add the role decorator and guards where we deem fit, but this is not hard to do. Additionally, manually implementing the user in the database is unrealistic, and we will at some point need to figure out how, when a user registers with Cognito, they can have their information logged within the database (default for VOLUNTEER, but also will need to create a new one for the pantry application approval, etc.).

See other comments from the description

Copy link
Collaborator

@sam-schu sam-schu left a comment

Choose a reason for hiding this comment

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

As discussed on Slack, best practice is to associate Cognito users with users in the database via the sub / user ID from Cognito, not the user's email (which can change!). Also, I don't think we'll need getUserRole or getUser in the auth service anymore (since role should just be stored in our database, and in our future signup flows the email should actually come to the backend first and we will have to pass it to Cognito when creating the user)

@dburkhart07 dburkhart07 requested a review from sam-schu January 24, 2026 03:48
UpdatePantriesTable1737906317154,
UpdateDonations1738697216020,
UpdateDonationColTypes1741708808976,
UpdateDonations1738697216020,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Was this intentional?

Copy link
Author

Choose a reason for hiding this comment

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

Strange, I thought this was no even there in the first place. I moved it back to the original spot

import { Order } from '../orders/order.entity';
import { OrdersService } from '../orders/order.service';

@UseGuards(AuthGuard('jwt'), RolesGuard)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The endpoint for creating a pantry application (and the one for food manufacturer applications, once that's merged) needs to be accessible without being logged in. Even though that endpoint doesn't have a roles decorator, the auth guard is requiring the user to be logged in (as anyone) for the request to go through. But it would be annoying to have to apply the auth guard to every individual endpoint except for those two. Is there a way we could set up the auth guard as a global guard (even better than applying it to every controller separately), but then have the ability to disable it for particular endpoints with something like @NoLoginRequired?

Copy link
Author

Choose a reason for hiding this comment

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

I added in a new guard called public, which I think should do it. Let me know if you want changes to this, but, while it doesnt override the authenticator components that currently exist around all of our pages, as long as it is the only guard on an endpoint (which it always should be), it will give access. I tested it by just putting it onto an endpoint and disabling it's authenticator for the page, and it allowed us in when enabled, and said we were unauthorized when disabled.

@sam-schu sam-schu removed the on hold label Jan 24, 2026
@dburkhart07 dburkhart07 requested a review from sam-schu January 26, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants