How to handle httpOnly cookie authentication in next.js with apollo client - javascript

In my usual experience all single page apps I worked on used JWT as authentication mechanism. I came across api that uses httpOnly cookies for this.
Since we can't access such cookie via javascript to know if it is present or not, how does one handle this in react app?
My initial idea was to track this by setting some sessionStorage upon successful sign in and removing it if I receive an error related to authentication.
But this doesn't work well with next.js server side rendering I believe? We have it set up with apollo client which allows setting custom headers and cache.
Is there a common way to handle this authentication process with set up above?

httpOnly just means that the value can't be read by JavaScript.
So you make an HTTP request to the server and it will return a response with a Set-Cookie header.
Then any future requests will automatically include the cookie.
(Just make sure that you set withCredentials or the equivalent.)

Related

Browser not sending cookies to APIs within same domain

I have APIs deployed in 2 separate namespaces, admin.abc.com (original) and api.admin.abc.com (new). Upon completing login, a jwt cookie is set by the admin.abc.com site for .admin.abc.com. The cookie is for the Session, Secure and HttpOnly and it does not have any SameSite restrictions set. This cookie works well for all API calls to admin.abc.com.
Due to some new changes requiring some services to deployed separately, there are now services also deployed in the namespace api.admin.abc.com. I have a status page that gives me information about the services, memory, etc. and it works fine for the original namespace. But when I add in a status call to the second namespace, no cookies are passed along and the call fails authentication. However, if I open the URL in another tab in my browser, the cookies are passed to the backend and the call succeeds.
I've looked into setting my own cookies in the header during the request after retrieving the jwt's value. This fails for 2 reasons: 1) I don't believe I can access the jwt's value as the cookie is HttpOnly, and 2) I can't set a cookie in the HttpHeader options. I get a "Refused to Set Unsafe Header" error in the console if I try. If I turn on 'withAuthorization:true', it does set it (but still prints the error) and then actually gets a CORS error.
If I manually disable the HttpOnly flag on the cookie using EditThisCookie extension, the calls work successfully. I don't really think it's an option for me with my company's security, but it technically works.
I know some suggest sending the jwt as an Authorization: Bearer token, which I would do, but because the cookie is HttpOnly, I can't retrieve the value and programmatically set it. Additionally there is another site-wide cookie I'd like to send over at times that I can't really switch to some custom header.
Thoughts on what I can do? Is it not possible? My only other option is that all requests to the new namespace have to be routed through an API built in the old namespace.

Is using cookie-based authentication in a cross-domain app a security risk?

I see that it's almost standard today to use JWT for modern applications, where the API and the front-end are totally separated, and served from a different server.
I know that browsers will not send cookies to different domains by default, but this can be overcome easily by setting the correct headers in the backend, and configuring the HTTP client accordingly, for example with Axios:
withCredentials: true
And express:
res.setHeader('Access-Control-Allow-Credentials', true);
This allowed me to use express-session, even though the frontend sits on localhost:3000 and the backend on localhost:8000(treated as cross-domain).
The question: Does this pose an increased security risk(cookie being stolen and used, for instatnce) over a JWT token, stored in localStorage? I mean, with cookies you can at least set the httpOnly attribute(what express-session does by default), which will block JS from using it, but with localStorage this is obviously impossible, being that you have to get it via JS.
Any clarification will be greatly appreciated.

Should the OAuth2 Redirect URL be to the frontend or backend?

I'm setting up OAuth2 in my app using the Authorization Grant flow. I am also using create-react-app, such that I'm developing on localhost:3000, which proxies to my app server backend on localhost:8080.
Everything mostly works, except for the fact that I cannot get the CSRF token working.
I realized it was because I was having the OAuth2 Redirect URL set to the backend, and as a result it was not sending the private encrypted csrf_state cookie along, because the request was originating from google instead of my app.
I don't think this will be a problem in production, because there won't be a proxy server. Instead, both the backend and frontend will be served from the same mydomain.com
So, should I just not have this work in development? Or should I have the OAuth2 redirect URL set to my frontend (localhost:3000), which then automatically redirects to the backend (localhost:8080), such that it can send the private encrypted CSRF token along?
Or is there a way to have the cookie originate from google, without having the multiple redirects? Or should I just not bother with CSRF, since SameSite has such large support amongst browsers now?
The OAuth2.0 Authorization Code grant includes CSRF protection using the state parameter. Use this instead of relying on cookies.
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
Source: https://www.rfc-editor.org/rfc/rfc6749#section-4.1
Ahmad is right - and here is some more context on standard usage for react apps and APIs:
If you're using React then you have an SPA that should redirect directly to Google during logins
So your redirect url should be localhost:3000
Your SPA should be entirely cookieless - and much simpler - which is one of the benefits of SPAs - also you can turn off CSRF checks in the API
Your SPA will then send an access token to your API and the API will need to validate the token rather than cookies
My tutorial and code sample may help you understand the moving parts:
https://authguidance.com/2017/09/24/basicspa-overview/

How do I securely store JWTs with Next.js and a Rails backend?

I'm trying to implement auth between a Next.js frontend and a Rails API backend, and I'm having difficulty understanding the how to properly do this securely.
I'm using the jwt-sessions gem on the rails side, which upon login returns an access_token, a refresh_token and a csrf token. The csrf can only be sent via header, and is required for all non-GET or HEAD requests. The access and refresh can be given via header or by cookies.
I can implement the backend to either return all the tokens in a JSON response, or set cookies, or a mix of both.
The problem is that, client-side I see no real way to store these tokens that is:
Secure
Available during SSR as well as in the browser
Here are the options I see
Option 1: Server-Set httpOnly Cookies
This is what the jwt-sessions gem recommends. Keep CSRF in localStorage, but the other tokens in httpOnly cookies set by the server.
This seems the most secure, but then SSR via getInitialProps won't work at all, because it can't send the cookies via fetch. I can't even manually send them because I can't see them in JS.
Option 2: non httpOnly Cookies
This is what the official next example seems to do (albeit with a single, probably stateless token)
Either have the server or the browser set the cookie without httpOnly.
I can access it via SSR, but doesn't this open me up to XSS?
Option 3: Use Browser Storage
Just put all the tokens in localStorage and/or sessionStorage. This seems to be the worst option, as it won't work in SSR, and to my knowledge is not secure.
????
Am I missing something? Is non-httpOnly, like in the official example, OK? Is there a better approach? Or will I have to ignore SSR (and thus one of Next.js's killer features)?
Lets examine each of the tokens and where it will be used. Cookies can only be presented to the server that set them, so any cookie will need to be set by the server that uses it.
refresh token
The refresh token will be presented to the jwt-sessions authentication server to get a new access and csrf token if the access token expires. Since the refresh token both comes from the authentication server, and is used on the authentication server, it can be set as an HttpOnly cookie by the authentication server
Set-Cookie: refresh-token=...; domain=rails-backend.example.com; secure; HttpOnly
access token
The access token will be used in two ways, it will be presented to the rails backend by the next.js client side, and it will be used by the next.js server side to make calls to the rails backend to server-side render pages via getInitialProps.
Presenting the access token to the rails backend from the client side is easy. The jwt-sessions authentication server runs on the same server as the rails backend, so the authentication server can set an HttpOnly cookie.
Set-Cookie: access-token=...; domain=rails-backend.example.com; secure; HttpOnly
Presenting it from the next.js server-side is more difficult, to get it there the client needs to send the access token to the next.js server-side. A header would be the appropriate way to present it initially. And to send it to the next.js server side, the client side needs the access token included in the json response from the authentication server.
{
'access': "..."
}
Host: next-js-server-side.example.com
X-Rails-Backend-Access-Token: ...
Once the next.js server-side has the access token, it can set an HttpOnly cookie to have the access token available for future requests.
Set-Cookie: rails-backend-access-token=...; domain=next-js-server-side.example.com; secure; HttpOnly
csrf token
The csrf token is used to protect requests to the rails backend from cross-site forged requests. It needs to be presented by anything that will make a request to the rails backend, both the client-side and server-side.
The client will get the csrf from the authentication server as part of the json payload, because that's the only safe way to get it to the client.
{
'csrf': "..."
}
The client can use it right now to make requests to the rails backend, but it won't be stored durably.
To get the csrf token to the next.js server side, the client will send it as a header
Host: next-js-server-side.example.com
X-Rails-Backend-CSRF-Token: ...
The next.js server side has the luxury of turning around and setting an HttpOnly cookie to have the csrf token available for future requests.
Set-Cookie: rails-backend-csrf-token=...; domain=next-js-server-side.example.com; secure; HttpOnly
This cookie is useless for cross-site forging requests to the rails backend, because it's only presented to the next-js server-side, and isn't presented to the rails backend at all.
The next.js server side can give this token back to the client, as long as it does it in a way that it can't be hijacked by a site mounting a cross-site forgery attack.
<input type="hidden" id="rails-backend-csrf-token" value="...">
Cross-site request forgery for next.js
The next.js server side will need to provide it's own cross-site forgery protection. It can't rely on the HttpOnly cookie it set above, because that will be presented in cross-site requests.

Where to store the access token so it cannot be vulnerable to XSS

I am developing a web app and I am on a process of handling the Authentication of user. I am able to get the access token by initially making HTTP POST call to the API.
The idea know is where to store it so that it is not vulnerable to XSS.
Have heard of HttpOnly Cookie which makes it possible that the cookie is readable by the client (javascript), but the thing is why would I need something to store on my end when I cannot access it. How do I sent back to the backend (API) access token stored into coockie with http only since I cant get it so I am unable to attach it into headers of my http request.
Also, can I add a coockie with flag HttpOnly using javascript?
HttpOnly means you cannot read it from Javascript on the client but the browser is still aware of that cookie and it is being added to any subsequent request you are making to the server.
It can be useful for an auth session cookie for example.
Session cookies are usually opaque tokens that are meaningless to the user and are meaningful only to the server. Thus there's no real reason why the client code would need to read them
Also, can I add a cookie with flag HttpOnly using javascript?
No

Categories