How is Clerk actually secure if the session in non-http? #336
Replies: 6 comments 2 replies
-
@osseonews this is a great opportunity to elaborate on Clerk internals. Let's start with an overview of how Clerk works. Clerk uses a long-lived, SameSite Lax, HTTP-only, secure cookie that is set on your Clerk Frontend API origin. This cookie is the main authentication cookie. It is used to generate short-lived JWTs that authenticate the API of the host application. The duration and the expiration of the long-lived cookie are fully configurable. Developers usually tweak them according to the security requirements of their applications. In essence, this setup is similar to a standard refresh/access token flow (e.g. OIDC). The access token is used for accessing securely an API, whereas a refresh token generates a new access token when it expires or even before. Clerk Frontend API has the necessary safeguards to prevent CSRF attacks by accepting requests only from the main origin of an application. In XSS things get more interesting. It is true that if a JWT is stolen, then the thief can access the host API. An API that accepts JWTs does an offline, stateless verification without depending on the authentication provider server. This is the most scalable and unobtrusive token verification method. In most cases, no communication is required between the host API and the authentication service making the token verification last about 1 ms. However, the host API can't know if the JWT is stolen or expired. That's where the expiration value comes into play. It is mostly a mechanism to reduce the window of opportunity and ensure that JWT authentication remains stateless. In Clerk, the JWT is truly short-lived as it lasts for 60 seconds. Added to that, caching the short-lived JWT in a As you correctly pointed out, the JWT can be set on an HTTP-only cookie by the host application. To do so, the developer should use Clerk SDKs on the server side, contact the authentication service and sync the session state across the long-live and the short-lived cookie. This is a backend-first approach that works with Clerk but also requires more work on the host API. Clerk is an off-the-self, easy-to-use, frontend-first authentication product. In the modern web, JWTs need to be handled in the browser for many reasons such as accessing Hasura or Supabase databases from the browser. To sum it up, there is no secure way to store a JWT on the client. Developers have the responsibility to sanitize user input as they always did. Let me know if this helps. |
Beta Was this translation helpful? Give feedback.
-
Adding a bit here: Clerk cannot unilaterally secure an application against XSS attacks. Preventing XSS is primarily the application developer's responsibility. However, Clerk can help "mitigate" XSS attacks by providing a session management solution that limits an attack's severity. To add clarity here, let me first address a small mistake in your question:
When an XSS attack is occurring, every user visiting your application is compromised. This is true regardless of the session management mechanism - we must assume the attacker's code can run arbitrary As a result, the top priority during any XSS attack is always eliminating the vulnerability. Your users are compromised for at least as long as the vulnerability exists. Any actions during this time period cannot be trusted, since it may be that the attacker is acting on behalf of your users. The big issue Clerk worries about is: how long can an attack continue after an XSS vulnerability has been patched? After an XSS vulnerability has been patched, the primary concern is whether session tokens may have been "exfiltrated," or saved by the attacker for future use. If session tokens have been exfiltrated, XSS attackers can theoretically act on behalf of your users for as long as the tokens last. This is catastrophic because tokens are often configured to last for weeks or months, so developers that suspect exfiltration occurred will normally revoke sessions (a.k.a: force sign outs) to stop the attack. HttpOnly cookies are helpful because they prevent exfiltration. XSS attackers cannot read HttpOnly cookies, since an XSS attacker can only run Javascript from the browser, and since HttpOnly cookies cannot be read from the browser. If only HttpOnly cookies are used for session management, an XSS attack is stopped immediately after the vulnerability is patched. This is the ideal case. At Clerk, HttpOnly cookies are indeed our primary defense against session token exfiltration. We store long-lived session tokens in HttpOnly cookies specifically to prevent XSS attacks from being prolonged. However, we also have a short-lived session tokens which are not stored in HttpOnly cookies, but instead in a traditional Javascript cookie. These tokens can be exfiltrated, so we needed to secure them another way. The security mechanism for these tokens is that they only last for 60 seconds. So while it's possible for an attacker to use these tokens after the XSS vulnerability has been patched, they can only act on behalf of your users for a maximum of 60 extra seconds. In the context of an XSS attack, 60 seconds is quite small. XSS vulnerabilities are usually open for hours before developers identify, patch, and deploy a resolution, so 60 seconds will not significantly prolong an attack. Why do it at all? JWTs on the frontend enable highly composable frontend services, and have become an industry standard. It's not just Clerk using them...database tools and graphql apis very commonly authenticate using script-accessible JWTs. We feel short-lived tokens are the only way to achieve both high security and highly composable frontend services. |
Beta Was this translation helpful? Give feedback.
-
Thank you very much for the detailed answers. While I completely understand why you decided to store __session access tokens in non-http mode (so they can be exfiltrated), I don't think it is the best long-term solution. If you look at libraries like next-auth.js or Anyway, I love what you have done with Clerk. It is an amazing service. I just hope that you reconsider the access token storage going forward. For NextJs applications, which it seems you are targeting, setting up the API route to verify the tokens, like Auth0 and NextJs do it, whether short-lived or long, is actually easier and more secure than your current approach. I think even for other frameworks other than NextJS it will be easier, b/c most frameworks nowadays (i.e. Remix) copy NextJs and also server calls in the app itself, so any of these apps will be able to read the token internally from a server endpoint. I would also mention that in our application we are authenticating API endpoints in NextJS by wrapping them in withAuth with @clerk/nextjs/api. It's simple and works very well. I don't see why this can't be done as a general approach to verify a short-lived access token that would be stored as http-only, or why this would require a major refactor on your end. |
Beta Was this translation helpful? Give feedback.
-
Can you help me understand the concern here? Once the XSS attack is resolved, we believe the attacker will no longer be able to retrieve additional short-lived tokens. How do you envision they retrieve a new one after the vulnerability has been patched? |
Beta Was this translation helpful? Give feedback.
-
Yes, of course, once the XSS attack is resolved the attacker can no longer retrieve short-lived tokens. But, that's the same irregardless of whether your short-lived access token expires in 24 hours or 60 seconds. My point is that making the access token refresh every 60 seconds doesn't seem to provide any real security benefit, unless the assumption is that an XSS attack will be discovered in the first 60 seconds. This is extremely unlikely. Most likely the XSS attack will be ongoing for quite some time before discovery, in which case the 60 second expiration didn't help. What would help is just storing the short-lived token in an http-only cookie at the start and reading it via an API call, like they do in the other popular libraries above, and like you seem to basically already implement in some fashion via the withAuth wrapper. |
Beta Was this translation helpful? Give feedback.
-
Just to make sure I understand things correctly:
Questions
That is, when using Thus, if the session cookies is stolen, it can only be used for 1 minute.
|
Beta Was this translation helpful? Give feedback.
-
Sorry in advance if this is a dumb question, but in reading the docs for Clerk, apparently Clerk secures against XSS attacks by creating short-lived session access token (__session cookie). But, how is this secure? If an attacker gets the short-lived __session access token (because Clerk specifically stores this in non-http cookie), then they can hijack the session, send the token to Clerk, and take full control of the user. So who cares about how long the session token lasts? If an attacker gets just one token (and this is quite easy to do no matter how short the token expiration is), they have taken over the session and the user is compromised.
Now I understand why Clerk stores the session in non-http, because otherwise the frontend apps would never work as designed (can't access http-only cookies client side), but that doesn't make secure. In theory, the only secure way to do this is to store the access token in http-only and then verify it by proxying all requests thru a server same-site to get the token for third-party requests. Why is this not done, other than that it is more complicated to set up?
Beta Was this translation helpful? Give feedback.
All reactions