-
Notifications
You must be signed in to change notification settings - Fork 0
Role based auth backend #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
sam-schu
left a comment
There was a problem hiding this 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)
| UpdatePantriesTable1737906317154, | ||
| UpdateDonations1738697216020, | ||
| UpdateDonationColTypes1741708808976, | ||
| UpdateDonations1738697216020, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this intentional?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
ℹ️ 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:
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:
@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 permittedpantries.controller.tsfileAuthModule, as this provides access to theJwtStrategyandAuthService, whch are both needed. See how thePantriesModulehas 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
Accessing food request form as an admin


🏕️ (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