-
Notifications
You must be signed in to change notification settings - Fork 0
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
Added access tokens to GraphQL request headers, updated middleware to handle authorization #60
Added access tokens to GraphQL request headers, updated middleware to handle authorization #60
Conversation
…sing client component and graphql hooks
… the get-token route
… and added some comments to the client-side-graphql component
// Secret is hardcoded for now | ||
// TODO: Need to figure out where the secret will be stored | ||
const secret = Buffer.from('Zn8Q5tyZ/G1MHltc4F/gTkVJMlrbKiZt', 'base64'); | ||
/** |
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.
Changed to using 'jose
' package to verify the token. This is because the "Edge runtime environment" does not include the 'crypto' Node.js core module, and jsonwebtoken relies on this module for cryptographic operations.
The 'jose' package is designed to work without this, using standard APIs like Web Cryptography API which is supported in browsers.
|
||
expect(redirectSpy).toHaveBeenCalledWith(`${process.env.NEXT_PUBLIC_BASE_URL}/login`); | ||
expect(redirectSpy).toHaveBeenCalledWith('http://localhost:3000/login'); |
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.
Just wondering if there is a reason to hard-code the localhost url here? Keeping it as an environment variable might be preferable, especially since every dev might have their environment set up differently.
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.
Ah, reading further into the code, looks like all of this is inside the context of a test and mocking the request/response. If this is the case then it's understandable to use a fixed url.
expect(request.cookies.delete).toHaveBeenCalledWith('dmspt'); | ||
expect(redirectSpy).toHaveBeenCalledWith(`${process.env.NEXT_PUBLIC_BASE_URL}/login`); | ||
expect(deleteCookie).toHaveBeenCalled(); | ||
expect(redirectSpy).toHaveBeenCalledWith('http://localhost:3000/login'); |
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.
Same as previous comment on hard-coded url. There are more in this document, but I'm not going to flag each individual one :)
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.
Yes, @andrewebdev as you mentioned above, I mocked the request and passed in the URL so that I could check the redirect url.
(getAuthTokenServer as jest.Mock).mockRejectedValue(new Error('Test error')); | ||
|
||
const response = await GET(); | ||
expect(response.status).toEqual(405); |
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.
405: Method not Allowed
doesn't seem to be the correct error code for this case. The test doesn't seem to check if the correct method (GET, POST etc.) is used.
Further down, the error message also reads as a Internal Server Error
which should be a 500
code. So it feels like things aren't matching up here.
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.
Thanks @andrewebdev. The 500 error code totally makes sense to me since it's on the server side. I think I was copying and pasting and forgot to change that one.
app/api/get-token/route.ts
Outdated
|
||
} catch (err) { | ||
console.error('Error fetching token:', err); | ||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 405 }); |
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.
From the use case here it seems like 500
should be the correct code to use.
What would be the cases where a token cannot be found? We probably need to handle each of those cases with different error responses.
If the token cannot be found cause a fault on our side, then 500
would be the correct code. But if it cannot be found cause the user isn't signed in, then 401
is probably the correct code.
But most likely the status 405
here is not the right one, since the method is technically allowed, so long as you have a token.
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'm still getting used to JS, so not sure if this is a good pattern or not, but would adding a try catch block within the getAuthTokenServer()
function that throws a more specific error that includes a code based on the what happened (like there was no token, the token is invalid/expired, etc.). Then check if there is a code already on the error, if so use it, otherwise it's a 500
?
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.
Thanks @andrewebdev. I will change it to statusCode of 500.
lib/server/auth.ts
Outdated
* @returns | ||
*/ | ||
export function getJwtSecretKey(): Uint8Array { | ||
const secret = process.env.JWT_SECRET |
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.
Just a side note: We should probably set up a mechanism somewhere to easily rotate secret keys if needed. It's good practice to rotate them every so often to improve site security.
@briri , we should probably create an issue for this so we are reminded of it at a later stage?
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.
My thought was to store this and probably other variables we have yet to identify in AWS Secrets manager which you can configure to rotate on a schedule.
I created issue 153 in the infrastructure repo to track the SecretsManager work
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 created issue 153 in the infrastructure repo to track the SecretsManager work
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.
Thanks @andrewebdev and @briri. Using AWS Secrets Manager is a great idea. I'll add a placeholder function for now.
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'll also create a ticket to use Secrets Manager in the NextJS app.
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.
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.
this all looks good to me
app/api/get-token/route.ts
Outdated
|
||
} catch (err) { | ||
console.error('Error fetching token:', err); | ||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 405 }); |
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'm still getting used to JS, so not sure if this is a good pattern or not, but would adding a try catch block within the getAuthTokenServer()
function that throws a more specific error that includes a code based on the what happened (like there was no token, the token is invalid/expired, etc.). Then check if there is a code already on the error, if so use it, otherwise it's a 500
?
} | ||
}; | ||
|
||
export const POST = () => { |
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.
these are great. I like that we explicitly block them
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.
@briri That's a good question about adding a try-catch in getAuthTokenServer(). I thought about it but wasn't sure it was necessary because it is always returning a value or null, and I wasn't sure of the likelihood of any error happening when just getting the cookie itself.
I added a try-catch to the GET request for any possible issues that might occur outside of the getAuthTokenServer though.
lib/server/auth.ts
Outdated
* @returns | ||
*/ | ||
export function getJwtSecretKey(): Uint8Array { | ||
const secret = process.env.JWT_SECRET |
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 created issue 153 in the infrastructure repo to track the SecretsManager work
let token; | ||
|
||
try { | ||
//Server side |
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.
Thanks for noting the different contexts here. I personally am going to need these clues so I know whether something is happening server or client 🙃
… and revised error status code type to 500 for get-token
@andrewebdev and @briri thanks so much for your code reviews. I really value your input. I've made the changes you requested so far. Please let me know if there is anything else you recommend that I update or add. I'm sure we'll discover that we need more changes as we progress with the project as well. |
Hi @briri and @andrewebdev, I consolidated my es lint configs into .eslintrc.json and fixed some linting issues that weren't caught with "npm lint". For now, I added "npx eslint ." to the "lint" script until I can figure out how to capture all directories. I also updated some package versions. |
Issue: #55
lib/graphql/client.ts
andlib/graphql/apollo-wrapper.tsx
--
utils/authLink.ts
- Creates 'authLink' for the creation of the client instances--
utils/cookiesUtil.ts
- Contains adeleteCookie
method--
utils/getAuthTokenServer.ts
- Gets JWT token from cookieTo Test authentication and authorization:
dmsp_backend_prototype
. (Make sure the JWT secret is the same for backend as frontend)/signup
. Signup with a new user./login
and sign in with new user. Notice the new 'dmspt
' httpOnly cookie that was added/dmps/10.48321/D1SP4H
. There are currently errors getting data for the page, but you should be able to acces that page.dmspt
' cookie and try navigating to/dmps/10.48321/D1SP4H
. You should be redirected to /login pageTo Test Token in Header for Client Side:
/client-graphql-test
and look at your Network tab and filter for graphql. You should see the token in the Authorization header