-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Sensitive information are being persisted in local storage #3436
Comments
Is this being ignored? or is there somewhere we can track the status of this? |
We are evaluating this. This argument can be ambiguous as well. For example see thread here: https://www.reddit.com/r/Frontend/comments/cubcpj/local_storage_vs_cookies_for_auth_tokens/ in addition to the comments/discussion in the above thread you referenced. However, it's an interesting problem in this particular space (client-side/browser), with the ultimate solution of simply not storing / using a persistent session (only storing in memory, which you can do with the SDK currently). Obviously, the down-side to this is needing to login in every time the page is refreshed. This issue exists storing in any storage medium i.e. localstorage, cookies / even httpOnly; even though JS can't access httpOnly tokens, this cookie is still sent with every request, so if JS is injected into your app (considering the main argument on this is generally XSS), API requests will technically still be made as an authenticated user. That said, the underlying Cognito SDK is using temporary credentials, these credentials need to be refreshed and Signature V4 signed in order to make authenticated AWS API calls. Also, these credentials are scoped to your authenticated/unauthenticated identities. So essentially, if someone was to first access your computer, and get into your web browser, they would need to create a signed request using an AWS SDK with your secret key and access key before the credentials have expired. You can also invalidate these credentials in the event of this via the IAM console. However, it's still an ambiguous topic with problems in all areas. We will be leaving this issue open to track feedback on this as we evaluate the path forward and most secure approach available when utilizing a client-side / serverless architecture with amplify. |
Hi, thanks for reopening. I agree that the opinions and strategies here are not universally valid. In my case I am using amplify to manage authentication with cognito by just retrieving and using Access Bearer tokens to call API Gateway with cognito authorizers, so no IAM temporary credentials for me. However the access token is temporary as well and the same principle applies (the attacker could use the token just until it expires). My main concern is the refresh token, which has a much longer expiration time and can be used to reclaim fresh credentials until the attacker is spotted and the token are revoked. I will be much more confident by following a strategy similar to the silent authentication implemented in Auth0, or knowing that there is a good reason to have a refresh token in the localStorage of my angular SPAs. https://auth0.com/docs/tokens/refresh-token/current Otherwise, can anyone confirm if implementing the Authorization code grant pattern leverages the problem of storing such refresh token? |
Ah i see, Assuming you are referring to this as to what you are using? The temporary credentials I am referring to are the ones that are signing all your AWS requests i.e. secret key and access key. Are you using the Amplify Auth Category, initialized by the CLI with your project, as in, are the services (API Gateway, Cognito etc...) in your application using authenticated / unauthenticated IAM roles? |
Exactly, we are providing a manual cognito configuration to the amplify SDK for angular and we are using such cognito user pool as an authorizer for our APIs (both using direct integration and lambda authorizers depending on the microservice) We use just the user pool with an OAuth Authorization Code Grant, so amplify redirects to the hosted UI and parses the corresponding callback query param, it is already very nifty compared with our previous implementation without amplify. |
Ah ok understood now thanks! So, you are not using federated identities at all and user pools directly. I was actually thinking that potentially the lambda authorizer could be an interesting solution in this case, even to potentially add a layer of security. We are working on some authentication work now that's still in the design phase, so this is a good thread to add to these discussions, will track/keep this up-to-date here as we move along, thanks for the details. |
@jacintoArias do you have device tracking turned on in Cognito User Pools? This will actually add additional security to the refresh token which it will only work on the specific device: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-device-tracking.html |
Tracking the device will definitely improve the security of the refresh token and fits in our requirements, thanks for the recommendation we will give it a try. We are currently migrating from a lambda authorizer to directly integrating with cognito beacuse we precisely think that cognito's implementation of token validation could be more advanced than our own custom token validation. Basic authorization is being done by filtering the endpoints using oauth claims in the token. I will be very interested in knowing if If you think that a lambda authorizer is a better and more secure approach than leveraging the cognito integration directly... |
Great to hear! Yes i think the device tracking is a good solution here. For the authorizer i think the Cognito one is good for what you are doing, i was more referring to a possible solution where perhaps we could utilize to add a layer of additional validation, however the device tracking does a good job of locking down the refresh token per device. |
@mlabieniec |
What about SPA use-cases, requiring a user to stay logged in greater than 1 hour but no more than 8 hours? Would the Cognito team (I presume amplify works closely with them) take as feedback to please allow either:
Either of the above enhancements would be arguably safer since it limits the TTL of the very sensitive refresh token information. Secondary concerns that compound vulnerability:
Similar security concerns expressed here: |
@mlabieniec thank you. Just wanted to emphasize that my second suggestion: increasing access token expire is really the preferred solution in lieu of silent auth features being supported. Here's some additional useful context for the Cognito team. I hope they receive this feedback well:
|
@evbo I have discussed with the cognito team this feature and they have informed me that this is in their backlog as a feature. I do not have a completion date on this yet tho will keep this issue open to track progress from their side. |
The thing I find spooky is that, in addition to the content in the tokens themselves, Auth also stores UserAttributes which has all of your cognito attributes (even the custom ones) in it. So someone hacking the local storage later might only get expired keys, but they also get your name, phone number, e-mail. There's even more information in the idToken. At work we'd call this Personal Identifiable Information (PII) and, like others who made this comment, I think that storing that forever is a problem. You can avoid this by using sessionStorage (I guess) but like everyone has pointed out that's pretty inconvenient, too. I kind of feel like there's no good answer to this. |
I should add this, though for everyone who is nervous about this: if you call Auth.signOut() the local storage is cleared. |
It is important to emphasize that if your application is going to go through security tests with a company that trusts the owasp definitions, you would have serious problems with the output of your project. Keep that in mind! It seems to me that aws and owasp have a lot to talk about... |
I would like to see an alternative storage that is cookie httponly , and amplify could create the lambda to manage the oauth flow with the server, I would pay more infrastructure but it would be a beautiful option for some, this would bring the advantage of passing the strict security tests based on owasp from my company. Meanwhile, amplify escapes my use case. |
Using nextjs 13.4+, it stores it in cookies now!! I've been playing with it lately and works nicely so far: This package should do it |
I get the argument for storing refresh tokens, but why are the cognito user attributes shown in plain sight? |
Not sure how you got to this result, I'm using EDIT: My bad, I just found out that you can change token-saving mechanism. |
Any update on this ???? |
In case it helps anyone, I use react native and we have a similar problem in that the default is to use react native async storage which is not secure. I migrated to ios keychain and android keystore using the following and it seems to be working. We will shortly be getting this re-tested by a pen-test company so I can let you know if it meets their standards. Caveat that the below code is rough and not thoroughly tested yet.
|
I've also had a negative pen test report recently on Cognito's handling of secrets (specifically the refresh token), but this time I'm on a web application. I'm experimenting with using a web worker to hide the data as per Auth0's blog post: Here's a gist of an experimental web worker implementation. It works, but still has a race condition to resolve due to the nature of web worker comms being asynchronous. From my research, the web worker solution seems to be the only practical way forward in browser-land at present. If this code is useful to anyone then feel free to use it. The other idea I've got is to simply not store the secret at all. This could be done for the refresh token, but obviously not the access token. To do it, I've been toying with the idea of implementing some form of API Gateway + Lambda solution, where the app would register its refresh token to the server when it first gets it, and then it would call the Lambda via API to rotate its access token, by simply passing its access token and having it all happen server-side and return the new access token. I hope AWS is reading this. It's an almost 5 year old issue so I hold out no hope, but it's certainly not best practice as it is now. |
One more point to make on this topic. I see there's CookieStorage, and I initially went to use it but then realised that client-generated cookies offers no additional security over Local Storage. Cookies are only secure if set on the server with the secure and httpsOnly flags. |
If it helps anyone, I have an example SPA you can run that integrates with AWS Cognito. Originally it used Cognito tokens in the browser and I updated it to use the token handler pattern, to store all tokens in HTTP-only SameSite=strict cookies that are issued by utility APIs. This deals with malicious JavaScript threats. It doesn't use Amplify though, and requires a more complex flow with more moving parts. |
@sc0ttdav3y, I don't understand the difference between storing the refresh token in a cookie (so storing it at your server) vs the implementation above I'm referring to :
@sc0ttdav3y I'm also confused what the difference is between your web worker and using SessionStorage? Both methods store the data in memory and in both cases the data is lost when the browser is closed, if I understand correctly. |
@JacobDel There's two types of cookie. The one currently implemented with Amplify (CookieStorage) is a cookie set by JS and it's vulnerable to malicious JS, as any cookie set in JS can also be trivially read by any other JS running on the same page via You also mentioned the SessionStorage implementation — that stores your tokens in plain sight just like local storage, so any JS on the page can trivially hit up your secrets with It's different to the web worker solution I linked to because the web worker storage is not accessible by injected JS, while session storage is accessible. However, as it stands now the web worker example I offered is about the same as MemoryStorage, as both offer about the same security but no persistence. Auth0's article explains why web workers are interesting beyond in-memory solutions. I'm really interested in what @gary-archer has done. His solution sounds like it implements the httpOnly cookie by using a utility server to issue it. This is similar to my own server-side thinking, however, I'm trying to remain within the Amplify SDK if I can so I'm looking to do everything through a custom storage adapter. My thinking is to keep the access token stored as-is in local storage but offload the refresh token to a server endpoint, which would be protected via the access token (i.e. Cognito + API Gateway + Lambda). With some polling, I can keep the access token refreshed without storing the refresh token itself to the JS. And by doing it in a custom storage adapter, Amplify's SDK will continue to work. It's certainly an interesting conversation. Perhaps others subscribed to this issue have had the past 5 years to think of better ways than this? |
@sc0ttdav3y A "custom storage adapter" as you've suggested is interesting, but I'm wondering if there's another way to do this that completely avoids local storage. It could work something like this:
|
My team is developing a ReactJS Single Page Application (SPA) that uses AWS Cognito with Amplify for authentication. We have significant concerns regarding the secure storage of authentication tokens. The built-in options for token storage (localStorage and sessionStorage) are not ideal, as their contents can easily be accessed via client-side JavaScript. Using in-memory storage is not user-friendly, as it results in users being logged out upon every page refresh. When checking other platforms, we noticed that Auth0 defaults to in-memory storage but implements an under-the-hood mechanism for refetching tokens on reload, which prevents users from being logged out. While httpOnly cookies offer a more secure alternative since they cannot be accessed via JavaScript, we are aware that Amplify does not currently implement this option. As our application will soon undergo penetration testing, we are actively seeking secure solutions for token storage. |
@ivan1373 a workaround is to override the CognitoUserPool storage implementation new CognitoUserPool({
UserPoolId: env.userPoolId,
ClientId: env.clientId,
Storage: new CognitoStorageOverride(),
}); Then export class CognitoStorageOverride implements ICognitoStorage {
// set item with the key
setItem(key: string, value: string): void {
}
// get item with the key
getItem(key: string): string | null {
}
// remove item with the key
removeItem(key: string): void {
}
// clear out the storage
clear(): void {
}
} You can then save the data whereever you feel is safer. |
@ivan1373 / @Steven-MKN - cookie storage is available: I provided the solution here: const handleAPICall = async () => {
}; (Provider file) Amplify.configure(config, {ssr: true}); cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage({domain: 'example.com'})); export default function ConfigureAmplifyClientSide() { FYI @jiachen247 - you should close this as the solution is there. |
@kevoj7 cookie storage is not suitable to store refresh tokens. I think people are misunderstanding the way cookies work. Server-side cookie storage can support the When you combine this with fact Cognito has no single-use refresh token, refresh token rotation or other best practices, unwanted code accessing this data is a keys-to-the-castle issue. @jiachen247 this is not solved and this ticket should not be closed. |
@sc0ttdav3y https://docs.amplify.aws/nextjs/build-a-backend/auth/connect-your-frontend/manage-user-sessions/ |
@kevoj7 No, it doesn't. |
Today I found this on the AWS Security Blog, written in 2023: To recap, Amplify still does not currently have a secure way to store the refresh token that can also survive page refreshes, and there is reluctance by some to believe the current cookie storage in the library isn't actually secure. This blog post shows a proof-of-concept of how to work around this long-standing issue, which has also been put into code: https://github.com/aws-samples/api-gw-http-only-cookie-auth. I haven't yet explored this solution in detail to see how it could be made to fit with Amplify's JS library, but it looks like a promising approach. It would be awesome if this approach could be implemented within the Amplify library as a storage option. I hope to explore this over the coming months, but I wanted to share this today in the hope it might help drive this internally at AWS, as it looks like Cognito is finally getting some love after all these years and perhaps someone within AWS might have some time to look at this officially. |
Describe the bug
All cognito session tokens id, access and refresh tokens are being persisted into localstorage. This goes against all industry security best practice of storing sensitive infomation in signed httponly cookies.
To see why its bad practice this article presents a summary.
The text was updated successfully, but these errors were encountered: