Clerk cofounder here - appreciate the feedback and forwarding to the mobile team!
HN user
colinclerk
Cofounder https://clerk.dev
Email: colin@clerk.dev
Clerk cofounder here: I hope this isn’t a Clerk stakeholder! It’s definitely misaligned with our culture around not speaking about competitors and instead playing our own game.
I wrote the tweet that Brandur quoted...
I think the bigger shifts are that developers use Checkout more, and complete more admin tasks in Stripe's Dashboard. By providing end-user UX (for both customers and backoffice), Stripe has reduced the API surface area that most developers need to consume. These changes end up having no impact on them.
Appreciate that. We have many thousands of apps like yours on the platform, and I’m glad to hear the free plan is working for you.
Forgetting about your auth provider is indeed the dream!
Cofounder of Clerk. I agree - our pricing has been significantly reduced since this was written (partially in response to this post)
We do intend to build more products, but if you're using us for pureplay auth we shouldn't cost 1% of revenue at scale.
(As far as I know, we don't cost 1% at any scale, except very early startup days pre-revenue who opt not to use our free plan.)
Cofounder of Clerk here - we dramatically lowered our prices starting December 2023: https://clerk.com/blog/new-pricing-plans
10,000 MAUs are now included in every plan. We also now offer "First Day Free", so if a user churns in their first 24 hours after signing up, they are never counted as active. For GenAI, this has led to 60-70% of sign ups never being charged as active users.
Fun!
I just put our logo up. It took me two tries before it took, so maybe a twodollarhomepage :)
What's the stack?
Cofounder of Clerk here - we definitely want free plan users to be aware of this limitation - any suggestions to improve visibility?
On https://clerk.com/pricing , “Customizable session duration” is listed as a primary benefit of the pro plan, and in the chart we show that the free plan is “Fixed to 7 days”
Apologies we failed to make it clear before you started, that’s definitely not intended. We thought this was a good limitation for the free plan because it doesn’t impact your ability to learn if your product is resonating. If it is, and our default doesn’t work for your app, then we hope you can upgrade now that your product is validated. (It’s maybe worth mentioning that the default of 7 days was selected by copying Google’s session lifetime, also not meant to be nefarious.)
Hey - cofounder of Clerk here
Glad we were able to mitigate this one for our customers, but have also been a bit surprised this vulnerability hasn't been generating more chatter.
tl;dr: if you use Google OAuth, any XSS on your site can likely be chained into a long-lived account takeover. In a roundabout way, it works around the protections afforded by HttpOnly cookies.
You can mitigate by always redirecting to a URL with an empty fragment (#) if your oauth callback URL experiences any failure.
Answering 2 first because it's easier: Yes.
1. It's a great question and we're still learning the answer. But, I believe the approach is ~95% compatible, and the last 5% just needs tweaking on the margins vs a major overhaul. Let me try to explain...
First: SSR definitely makes the framing of "frontend api" vs "backend api" very confusing. So ignore that, and think of it purely as "api authenticated with a session token" vs "api authenticated with a secret key."
I think authenticating via session token is the key to enabling faster development with Clerk than tools like Auth0 (or even Stripe/Twilio/etc). The reason why is that it shifts the problem of _authorization_ from our customer's backend to Clerk's backend.
As an example, consider a user updating their email...
In the past, you would build a frontend for collecting their new email, send it to your backend, ensure that the user is updating their own name (the authorization step), then forward the update along to your account system (Auth0, your own database, whatever).
With Clerk, you build a frontend for collecting their new email, then send it straight to Clerk to handle the update with the user's session token. We are responsible for ensuring the update is to the users own account, and there's no requirement to hop to your backend to relay the secret key.
In the end, that hop to the backend and authorization check is responsible for a lot of the "clunk" that Clerk eliminates. And ultimately, SSR doesn't change our ability to make things easier – we can authenticate our API with a session token just-as-well during SSR as we can from the frontend.
This feels like a paradox, right? A session token has such limited power compared to secret key, so surely it can't be used to build an easier API. But in practice, confidently knowing which user is making the request is necessary for shifting the authorization step to our service.
I'd add that this idea isn't particularly novel. Stripe Checkout depends on a CheckoutSession object, which you initialize by passing in the active user's ID. So there, you see that Stripe having the active user's ID enables them remove a ton of steps for building a checkout. Implicitly, under-the-hood Checkout relies on an API that uses a session token for authentication.
We just took the idea one step further and are exposing the API, instead of only using it to power a single, fairly rigid UI. With Clerk, developers can use React Hooks to build their own UI.
---
Now, regarding the 5% that we still need to figure out. It pertains exactly to the currentUser() and currentOrg() functions you're calling out. Those are compatible, but they require some extra thoughtfulness.
As an example, Clerk's User object has a field called "privateMetadata". From the backend, it's completely okay for currentUser() to return this private data, but Clerk needs to make sure it doesn't leak to the frontend. That creates some oddities - the User object on frontend is different than on backend, and I don't think we've really nailed the ergonomics / education on this part yet. But it generally feels like a solvable problem.
Clerk | Frontend & Backend SWE | Remote or in-person in SF
Clerk is hiring frontend and backend engineers, remote or in-office in San Francisco
We build developers tools for authentication. We're known for our React components like SignIn and UserProfile that "just work" when they're added to the page.
Our components are powered by a new type of API: a frontend-facing API that relies on session tokens for authorization, instead of a backend-facing API that relies on a secret key.
We've found this pattern unlocks a new level of efficiency. Developers can implement Clerk faster than traditional APIs because it comes built-in with UX and UI. To learn more about the approach, see our talk on A Component is Worth a Thousand APIs:
https://www.youtube.com/watch?v=enUuBY3HXh4
We're especially excited to work with engineers who are thoughtful about speed and craft. Clerk is defining the gold standard for components-as-a-service, and we are constantly searching for new ways to evolve and improve our approach. Apply here:
https://jobs.ashbyhq.com/Clerk/308e77a2-872b-4835-aaf3-532bb...
Separately, I'm one of Clerk's cofounders, and I'll be watching this thread through the day if I can be help with any questions. Or feel to email me (colin@ our domain)
Does the crowd on HN expect passkey support to become more ubiquitous in the future, similar to Google OAuth today?
I’ve been surprised at how few sites seem to be adopting rapidly since there are UX gains, but I suppose Google had a fairly slow trajectory as well.
If anyone's facing this in their auth flows, we're happy to help at https://clerk.dev
We're in the same cat-and-mouse game with the attackers as everyone else, but since we're an auth company, we have full-time folks monitoring for issues and resolving when they come up.
It's worth mentioning that Twilio is in an understandably tough position here. They only receive API requests from your server, and real requests look the same as attack requests except for the phone number.
Clerk is in a better position to help because our API accepts traffic directly from the attacker (e.g. POST /verify-phone-number). We know their IP, user agent, whether they're connecting from AWS, etc, etc. We very much rely on this data to help stop them.
SameSite=Lax plus CORS does the trick.
Block requests where origin=helpdesk.mysite.com.
Also, since you're concerned about subdomain attacks, make sure you set the cookie on a subdomain rather than the naked domain to prevent it from leaking.
Edit: you can put it on the naked domain if your app is on the naked domain. If you do that, do not set the Domain= attribute in your Set-Cookie because that will cause it to leak to subdomains.
I don’t agree that the complex system is mediocre.
I like it because it’s faster and it’s enabling more powerful integrations.
We (Clerk) abstracted away the complexity so it’s just an implementation detail, and think other tools should do the same instead of letting developers trip over the hazards.
Usually people experience this when they're using a React frontend that's served from a CDN, while the backend is served from a separate service.
If that's the case, the pattern you're describing is pretty normal and not an inherent cause for concern.
If you really want to workaround it, you can do this:
First, host your backend and your frontend from the same origin, so the initial window navigation includes the httpOnly cookie (make sure your cookie is set to SameSite=Lax, NOT SameSite=Strict). Then, before the CDN serves your static content, run some middleware to ensure the httpOnly cookie is valid, and add some kind of marker to the page body to indicate they're signed in (or redirect away if they're signed out).
The not-so-trivial part is running middleware in front of the CDN, but that's possible with things like Cloudflare Workers, and these days it's built-in to Next.js for most hosts.
For this approach you'll also want to make sure you can check cookie validity quickly at the edge, or else you'll be slowing down your CDN a lot.
Disclosure: cofounder @ https://clerk.dev
Auth isn't "hard" per se, but it's still a big struggle because there's just so much to implement.
For example, one of the most common attacks today is "credential stuffing," which is simply the practice of taking leaked credentials and trying them on other websites.
Protection against this is arguably trivial: implement HaveIBeenPwned and a rate limiter, and create a mechanism to update the password when credentials leak.
But that's a lot of work, it can take a lot of tuning to get the rate limiter right, the UX/UI for updating passwords is time consuming, etc, etc.
And that's just one piece of authentication security – there are so many described in NIST 800-63B.
And then there's also the challenge of keeping up with user preferences - Sign in with Google, SAML, Touch ID, magic links, etc, etc. This seems to be fragmenting rather than consolidating.
Obviously biased, but all-in-all, I think you're better off sticking with a service than pulling it in-house, even if you stay with one of our competitors :)
In practice, almost all cross-origin API requests will require these preflight requests, notably including
At Clerk, we took the opposite approach, and restructured our API so it fits within the narrow window that does not require a preflight
I wrote a little about it here: https://clerk.dev/blog/skip-cors-options-preflight
Will edgedb ever launch frontend querying with JWTs like Supabase, Hasura, Fauna?
Nice, love that you launched with React components! I need to decide to show a button before I decide to allow a button's action :)
The main benefit is that we're much, much easier to use with React. Since the Okta acquisition, we've also had several migrate over for better pricing.
If you're interested in the technical reasons why & how:
Auth0 is structured as on OAuth provider, which is great for verifying identity but it stops there. Developers are responsible for persisting a session and managing user data on their own.
Developers using auth0 will normally create a "users" table in their database and have their backend team create endpoints to manage the data. Clerk handles all that for you... we expose useUser() to the frontend, and devs can call things like `user.update({firstName: "foo"})` to retrieve profile information. (Most teams using Clerk don't have a users table at all and use our API instead, though plenty still choose to keep it synced in their database.)
We're able to offer hooks like useUser() because we manage sessions, so we always know which user is signed in. As part of managing sessions, we include session revocation and device management - things that every company will need eventually but may skip over from the start. Because of this, we're often seen as more secure by default than auth0.
https://clerk.dev is hiring remote frontend engineers, US or EU timezones only, in-person in San Francisco if desired
We're a small team (<20) and have seen consistent customer growth since January
We build drop-in auth components for React: <SignUp/>, <SignIn/>, and <UserProfile/>
And corresponding hooks so developers can design their own: useSignUp(), useSignIn(), useUser()
Going forward, we've noticed that the last generation of developer tools have failed to embrace React, so we're building a unified platform of components and hooks.
Since we manage authentication, we can safely expose other devtools directly to the frontend. A proof of concept for this is our use-stripe-subscription, where we allow users to purchase and manage their own subscription through a useSubscription() hook: https://github.com/clerkinc/use-stripe-subscription
Email me (cofounder): colin@clerk.dev
The use case is just speed of development. We looked at several Billing implementations and found consistent patterns for exposing available plans and subscription management to the frontend.
use-stripe-subscription simply packages one implementation so your frontend team can consume it directly, instead of asking backend teams to reinvent the wheel.
It doesn't just re-expose Stripe endpoints, which would result in significant security concerns. This package clamps down the endpoints so the active customer can only see their own data and modify their own subscription.
Hey HN - in addition to the linked codebase, we wrote a blog about the general challenges involved with "refactoring" a backend-facing REST API to also provide a frontend-facing React API (of hooks and components): https://clerk.dev/blog/refactoring-stripes-api-for-frontend-...
Hey HN - we’d really appreciate any feedback on this one as it’s still in R&D.
One of the bigger questions is with the class name approach. As written in the document, we replace the default class name when the developer provides a string.
Another strategy is to append the developers string to our own. This might be better if our styles are already close and you just want to make a tweak, but it could also be annoying to tear down our styles if you don’t want them (e.g. removing our box-shadow)
We’d also appreciate any examples of embeddable components that you particularly enjoyed working with.
Thanks!
Yes, definitely. I think on modern stacks, costs are reducing to the point that most would rather avoid the webhook complexity.
It may be domain specific, too. It’s not all that common to need user profile data in the critical path. Auth is in the critical path, but we workaround that via JWTs.
Hello - I'm the cofounder of Clerk.dev. Congrats on your launch!
Indeed, we support password-based authentication. Developers can also choose social login, passwordless, or web3 (instead, or in combination). In practice, ~60% of developers are enabling passwords for their applications.
Our technology is agnostic and also supports traditional frameworks (node/express and ruby/rails have SDKs, python/django is under development).
We also support auth across N subdomains and mobile (currently just react native).
Perhaps surprisingly, US government guidelines exist, are pretty fantastic, and agree with the author:
Memorized secrets SHALL be at least 8 characters in length if chosen by the subscriber. Memorized secrets chosen randomly by the CSP or verifier SHALL be at least 6 characters in length and MAY be entirely numeric. If the CSP or verifier disallows a chosen memorized secret based on its appearance on a blacklist of compromised values, the subscriber SHALL be required to choose a different memorized secret. No other complexity requirements for memorized secrets SHOULD be imposed.
It's called NIST 800 63-B and available here: https://pages.nist.gov/800-63-3/sp800-63b.htmlShameless plug - I'm the cofounder of Clerk and we handle passwords in a sane way out-of-the-box: https://clerk.dev/features/passwords
I agree this is a concern, though more with phishing than social engineering.
An attacker site pretends to have their own "Login with HN" implementation, but asks users to put in a code generated from LoginWithHN.com itself.
If the user adds the code, then the attacker can impersonate the victim on any service that supports LoginWithHN.com (because of the special second-time login handling)
If the string was more explicit that it's for LoginWithHN.com, the victim is more likely to recognize that something phishy is going on.
Wow, awesome! We've had a few startups ask for an HN integration at https://clerk.dev and we'll build this in ASAP.
It would be great if this could somehow verify whether an HN account has been part of YC cohort. A few requests we've received were with the hope of offering early access to YC founders-only before a public release.
Also, I love the OTP solution instead of asking for our HN passwords.