keratin / authn-js

JavaScript client library for Keratin AuthN
GNU Lesser General Public License v3.0
45 stars 20 forks source link

Token refresh returns 401, does not work #36

Closed zekenie closed 3 years ago

zekenie commented 4 years ago

Hi,

We are using AuthN on a product and loving it. Thank you for building it. We're seeing an issue in prod where token refreshes made by authn-js aren't working. I spent a little while today understanding how the SessionManager works, and I think I'm seeing something unusual.

When authn-js attempts to refresh, it isn't even sending along the current jwt. The server gives us a 401 with no response body.

We're running on version 1.3.0 of the js client.

Initial session request - Request made at 3:02 local time - Sends creds - gets jwt - stores it - We use it and send it along with every request to our backend
Refresh at halflife - 3:32 local time, request to authn for `/refresh` - Response code: 401 - No response body - I notice there is no jwt being sent - No authorization header - No query param ![image](https://user-images.githubusercontent.com/962281/96510370-090e0080-1223-11eb-83ab-95456fb15f6e.png)
zekenie commented 4 years ago

Reading more code and trying to understand.... It seems like my browser (or authn-js) is supposed to send along the cookie that was sent with the initial session request. I originally disregarded the cookie, because I thought, "I'm using local storage, the cookie does not apply to me," but now I see that it's used to refresh the session tokens.

zekenie commented 4 years ago

I have set the SAME_SITE to NONE and I think it's working? I'm not sure if I understand the implications of this. Perhaps once I do I could contribute some docs. I thought I followed the decision tree perfectly, but I still found myself having this issue that was hard to understand

cainlevy commented 4 years ago

You're not alone! I still have to brush up on SAME_SITE from time to time, and I'd love an opportunity to improve documentation.

In theory, simple token refreshes should work with any SAME_SITE configuration. Based on the documentation I left for myself (hah!), that detail only matters with OAuth back-and-forth user flows.

I originally disregarded the cookie

Do you mean that you didn't look at configuration, or that you somehow stripped it from the request/response (e.g. with a proxy server)?

I'm not sure if I understand the implications of this

Using SAME_SITE=NONE enables the classic browser behavior where cookies are sent with every request to the domain. The other options are intended to provide XSS protection to sites that have the luxury of enabling them.

You should still be protected even with the NONE value. AuthN uses it for defense in depth, but depends primarily on request headers to mitigate XSS.

cainlevy commented 4 years ago

I take it back: the SAME_SITE config would also impact your refresh behavior.

if AUTHN_URL's site is different from any of the APP_DOMAINS' sites

The trick here (at least for me) is remembering that "site" is not the same thing as "tld". Your AuthN and application hosts are on different sites. More here: https://publicsuffix.org/

I'd take you up on a bit of extra documentation if you can remember any places where it would've been helpful to have this information.

zekenie commented 4 years ago

Hey!

To answer your q's:

"I originally disregarded the cookie" - I saw that the server was sending a cookie back as a header, but i thought "oh, I'm using local storage, this cookie doesn't apply to me." I didn't realize the cookie is how the client sent back the refresh token to authn

I think I just realized the way in which I misread the docs. When I read the decision tree (below), I misunderstood the first if statement to mean "if your client url is not in your APP_DOMAINS then, sameSite := NONE. I'm not sure what docs change would be helpful. Now that I read it really carefully the docs are correct.

if AUTHN_URL's site is different from any of the APP_DOMAINS' sites
  // We need the session cookie on cross-site requests for SSO
  sameSite := NONE
else if any OAuth providers are configured
  // We need the session cookie when returning from OAuth site
  sameSite := LAX
else
  // We only need the session cookie for client API requests after load
  sameSite := STRICT
zekenie commented 4 years ago

Honestly, thinking about it, what would have been really helpful is a console error. Either when /session gives a cookie that chrome will block, or when /refresh attempts without a cookie. The /refresh response body could also contain a payload describing the error. I'd be happy to do a PR for the JS side of it

cainlevy commented 3 years ago

I haven't discovered a way to action this yet.

The reason that AuthN uses a cookie to store its refresh token (aka authn-server session) is because the cookie can be hidden away from frontend JavaScript. This is important to secure the long-lived token away from potential XSS attacks. The implication, however, is that the frontend can't know when there is (or should be) a cookie. All it can do is try to use it.

The most recent version of authn-js does improve on 401 handling, though. Previously it was throwing an unhandled error when the refresh failed. Now it should properly handle that error and only throw in unexpected situations.

I don't know whether this explains anything you experienced. Figured I'd mention it in case someone else comes through here on similar search terms.