GithubHelp home page GithubHelp logo

Comments (14)

BartoGabriel avatar BartoGabriel commented on July 19, 2024 1

On the other hand, in a while I will do PR with the small correction.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

Can the token be refreshed silently, using a SPA and http-only cookies?

Silently as in without the user noticing? Don't think so, because with http-only cookies, you cannot access them in JS, so you need the browser to explicitly visit another URL: the token refresh URL, where the server (Lambda@Edge) will read the JWT and do the refresh. The user will notice this redirect, even though the user is redirected back immediately.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

BTW if you deploy the solution in static site mode, all cookies will be HTTP only by default.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

https://github.com/aws-samples/cloudfront-authorization-at-edge#spa-mode-or-static-site-mode

from cloudfront-authorization-at-edge.

BartoGabriel avatar BartoGabriel commented on July 19, 2024

Thank you very much for your prompt response.

I was doing some tests. I hope I am not mistaken in the conclusions.
Although it is as you say, the JS does not have the capacity to renew token when it expires. But there is one interesting property of the “fetch” function (JS) can be used, which is to set the “redirect” property to “follow “(by default browsers take it this way). https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch

This means that JS makes an API call, the lambda function checks if it is about to expire, if so, it forwards it to "/ refreshauth", where the lambda function silently updates the token from behind and returns 307 to URL of the original API (with the new cookie), due to the redirection settings of the fetch function it follows the new URL. Then the browser calls the redirect URL again, and finally the fecth function returns (with the original URL).
Sorry if the explanation is poor or not well understood. I don't have good english.

To test this, I made a simple application, where a SPA is loaded and every 5 seconds it calls an API and updates a textarea with the result code and a little part of the JSON. As I show you in the following image, the application called the API, it updated the token, and for the spa application this was transparent. There was also no unwanted refresh page:
imagen

I think this is a way to leave the token a little more protected in a SPA application.
In the event that the SPA needs the JWT payload, a lambda function could be made that only returns the payload without revealing any extra information.

What do you think of this solution? Are there any considerations that I'm not considering?

from cloudfront-authorization-at-edge.

BartoGabriel avatar BartoGabriel commented on July 19, 2024

On the other hand, looking at the following line of code, I think there is a small bug:
https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/master/src/lambda-edge/check-auth/index.ts#L32

I understand that what is intended is to update the token 5 minutes before it expires. If so, 60 * 5 should be added, not subtracted. Or in any case subtract the variable exp - (60 * 5). In this way, either compare it for a future time, or compare it for a token with less expiration time.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

60 * 5 should be added, not subtracted

Indeed, nice catch! Wanna send a PR for it and grab the street creds?

About your solution regarding refreshes. I can follow your general reasoning I think. But isn't a caveat that you would need to send all fetches to the refresh URL, and pass the actual URL you want to fetch to as a query param or so, so the refreshAuth function can return a redirect to it?

While that will work, it is quite demanding on the front-end code I guess.

from cloudfront-authorization-at-edge.

BartoGabriel avatar BartoGabriel commented on July 19, 2024

Sorry, I think you don't explain me correctly. I'm going to explain it in other words and add a new option. I believe that these 2 options could be implemented to make the token refresh from SPA, without exposing the tokens.
In both cases, in the authentication server it uses the “hybrid flow”, with a client secret (since it is only known by the server in the lambdas functions), and with offline access in the scope (to be able to refresh the token calling from lambdas functions). And Always the server is the one that updates the tokens.

Option 1

I will explain my previous solution again in other words, since my level of English did not help me.
To help my reasoning, I am going to use a different example from the one I mentioned earlier (POST method instead of GET method): A user has a form with a field that POSTs the URL “/api/machine”. Sending the name of a new machine. The post method in the API creates a machine and returns code 202 with the data of the created machine.

Assumption 1 - User has a token that expires in 20 minutes

  1. The user makes a post to the URL “/api/machine”, with the data of the machine.
  2. The lambda (check-auth) function verifies the token, as this is correct, it continues its course towards the API.
  3. The user receives an HTTP code “202”, with the information of the created equipment.

imagen

Assumption 2 - The user has an expired or expired token in less than 5 minutes.

  1. The user makes a post to the URL “/api/machine”, with the data of the machine.
  2. The lambda function (check-auth) verifies the token, as this is expired or soon to expire, and the user has a refresh token, redirect it to the lambda function that is responsible for renewing the token (refresh-auth).
  3. The browser receives a 307 (redirected to the lambda function). Because it is a redirect, the JS fetch function in the code if configured with the "redirect" attribute in "follow" (default value), calls the redirect without the JS code knowing.

imagen

  1. The browser redirects the post to the new address, which is serviced by the lambda refresh-auth function. The function is responsible for calling the identity server, and with the client id, the secret client and the refresh token, it requests a new token. If everything goes well you get new tokens (this is all from the lamba function). And again it sends a redirect (307) response to the original URL (“/ api / machine”), but with the new cookies.
  2. Again, the browser receives a 307 (redirected to the original / api / machine address).. Because it is a redirect, the JS fetch function in the code if configured with the "redirect" attribute in "follow" (default value), calls the redirect without the JS code knowing.

imagen

  1. The lambda function (check-auth) verifies the token, as this is correct, it continues its course towards the API.
  2. The user receives an HTTP code “202”, with the information of the created equipment.

imagen

imagen

Conclusion:

In both cases, the result for JS was the same:

imagen

Some considerations:

  • Regarding the overhead you point out, these redirects only happen when the tokens expire (for example, if it were every 20 minutes, 2 redirects would happen every 20 minutes, I don't see it as a problem). It would be great to be able to do it behind the scenes without JS suffering these redirects, but for that the lambda function edge should update the token, call the api, and change the cookies (request and response event), I am new to lambda Edge, but I understand that this cannot be done.
  • Is important to note that in JS the fetch functions must be configured so that the redirects automatically follow. In the case that they have another configuration, this would not work. With which it can be a great disadvantage, since it would be good for developers to forget about this. Or maybe the api returns redirect and the JS needs to see them, and this would be a problem.
  • The refresh-auth function must tolerate all HTTP methods (not just the get).

Option 2

Develop a new lambda function in a new URL, for example "/check-session". This function will verify that the token is correct and has not expired. With which you will have 3 ways:

  • Token valid and not expired: Only returns Ok (202)
  • Token valid, but expired or soon to expire: The function is responsible for calling the identity server, and with the client id, the secret client and the refresh token, requests a new token. If everything is correct, restart Ok (202), but with the new cookies.
  • Token invalid or renewal fails: Authentication error 404. This must be handled by JS. Here there is no point in doing a redirect, since the api is only called from JS once it is authenticated.

Then from JS, you must do what is currently done (call this URL every 5 minutes):
https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/master/src/cfn-custom-resources/react-app/src/App.js#L38

But instead of calling the authentication server, "/check-session" is called.

You can also add it to return the payload of the jwt, instead of returning an empty 202.

Conclusion:

I think this is the best option. It does not imply many changes, it is transparent to the JS, and the tokens are well secured in only http cookies.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

Option 2 is actually already supported now I believe (maybe you are saying that). You could do a background XHR GET to index.html or any other page every 5 minutes, and that would trigger checkAuth, and token refresh if needed.

Good suggestion! Maybe we should write a bit about it in README.md

from cloudfront-authorization-at-edge.

BartoGabriel avatar BartoGabriel commented on July 19, 2024

Thank you so much for your patience. I think it is important that this is found in the documentation.

One last point: Although the token is refresh, either in the form calling the identity server every 5 minutes, or doing XHR GET to index.html every 5 minutes. I think that the check-auth function is not correct, because it can give that it is called in a time window that the token has not been updated yet, and JS will receive a redirect without knowing how to handle it.
Example:
imagen

I think that the API calls should only validate that the token is valid or if it is invalid return 404. It is not something that should be changed in the project, but I think it should be kept in the documentation.

from cloudfront-authorization-at-edge.

deepku2 avatar deepku2 commented on July 19, 2024

Storing tokens in non HttpOnly Cookie is a security vulnerability. No enterprise would allow it. We went through security review and this approach was rejected by InfoSec. Same vulnerability exists for Amplify implementation where they are storing tokens in non HTTPOnly cookies.
Also RefreshToken should be stored at specific path (/refresh-token)

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

This solution here supports storing cookies as HttpOnly, and the linked blog post also goes into this.

It is all about trade offs !

Storing refreshToken at a specific path, if you want that, is possible too (ny the same mechanism). But it would need a small code change, because check auth lambda, which is attached to the default behavior, now checks to see if there is a refresh token before forwarding to the refresh tokens endpoint. That would not work, if check auth can't see the refresh token - so then it would send you to Cognito to sign-in again

Ultimately, the solution here is a sample, that you can go on from, intended to show how you could do it, and speed you up. By all means change it to fit your security requirements,

from cloudfront-authorization-at-edge.

apollo7483 avatar apollo7483 commented on July 19, 2024

Sorry for opening this issue again, I am using this template to deploy a react app for employees to use in a company. Using non http-only cookies seems not best security practice. But in my case, the website is used by trusted employees. The website is not exposed on the internet to used by untrusted users, so this is not an issue. Am I right ? I am sending this jwt to an api gateway with cognito authorizer enabled that located outside of this cloudfront.

from cloudfront-authorization-at-edge.

ottokruse avatar ottokruse commented on July 19, 2024

Making cookies http-only is a way to protect against malicious JavaScript. If somehow malicious JavaScript got deployed on your site, it could e.g. steal the credentials from logged in users.

But if your SPA needs to access the JWTs also because it uses them in client side fetches, then you have no choice but to make the cookies NOT httpOnly.

A middle ground is to just make the refreshToken httpOnly with cookie settings such as:

{
        idToken: "Path=/; Secure; SameSite=Lax",
        accessToken: "Path=/; Secure; SameSite=Lax",
        refreshToken: `Path=/refreshauth; Secure; HttpOnly; SameSite=Lax`,
        nonce: "Path=/; Secure; HttpOnly; SameSite=Lax",
}

But then, if your JWTs expire, you must make some request to CloudFront so that Lambda@Edge triggers and refreshes your cookies (because you can't do it client side anymore, as you can't access the refresh token there). Such as: #190 (comment)

from cloudfront-authorization-at-edge.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.