998 karma · joined August 19, 2019
Email: colin@clerk.dev
Apparently, since developers should already have been protecting against cross-origin requests on GET and POST via other means (CSRF tokens), there was no need for the additional preflight protection.
They only added preflights to requests that browsers couldn't make previously.
2021 was an exciting year for Clerk and for authentication more broadly. I wrote this to share where we see traditional auth solutions missing the mark, and a bit about our expectations going forward.
It's been an exciting space to work in, especially as technology stacks are actively changing around us. If auth challenges interest you, we're hiring: https://apply.workable.com/clerk-dev
We - and I'm sure every other vendor - limit OTP tries.
The guidelines for authentication are very well-defined by NIST 800-63B. For example:
> In all cases, the authentication SHALL be considered invalid if not completed within 10 minutes.
https://pages.nist.gov/800-63-3/sp800-63b.html#5132-out-of-b...
Depending on the exact type of OTP, NIST may have different guidelines.
Aside from that, though, I think it's reasonable to argue that the security of password+code in 1password is equivalent to just password in 1password.
Edit: Didn't answer the actual question - it's something we can look into. My instinct is that offering this wouldn't drastically change the security model, as long as we can be confident your password actually came from a secure password manager. Since some password managers (like 1password) are very strongly tied to devices, I think your ability to retrieve a password from it is a reasonable proxy for a possession factor.
It's definitely something I'd want to read more literature on before building. That's just my instinct, and I'm half expecting someone on HN to share the attack I'm forgetting :)
It's a sordid affair, but we're making progress. We've reduced our average time to sign-in by about 20% since our launch 6 months ago. (There's nothing to say our starting point was very good, but we do think about this very consciously.)
If you're working to improve your sign-in flow, our biggest wins so far have been:
- OAuth buttons at the top, critically with Google included. OAuth is _way_ faster than passwords for most users, putting it at the top switched oauth usage from just below 50% to just over 50%. Those extra percent using oauth bring down the overall average speed.
- Eliminate OAuth "edge cases." Turns out, they're not edge cases at all. 15% of users will sign up with email/password then try OAuth next, or will sign up with OAuth then try email/password next. Make sure you have happy paths for these.
- Magic links instead of OTPs for passwordless auth. Overall, magic links are a few seconds faster than OTPs since there's no entry step. (That said, we're still investigating whether it's better to trigger OTPs on mobile devices because of the auto-fill capabilities)
- Integrate with password managers. Our sign-in flow is normally two screens, but if we detect a password manager we'll accept the password on the first screen. Password manager folks are already the fastest, but this makes them even faster.
This is just the first factor. Admittedly, our second factor is still lagging behind, but UX is getting better for 2FA with FaceID & TouchID. We're optimistic we can have a positive impact on the second factor, as well.
If you're interested in having a team obsess over this on your behalf, come check us out :)
The MVC way was:
1. Look at the route
2. Recognize that this route has, e.g. A Header, A Sidebar, and Content
3. Load the data for the Header, Sidebar, and Content
4. Pass the data to the templating engine
5. Use the templating engine to render the route, which likely fans the data for the Header, Sidebar, and Content into individual template files.
Remix highlights that Header, Sidebar, and Content are three separate components. Instead of loading the data all at once (#2 above) and then rendering all at once (#5 above), it allows you to load and render each component individually.Others:
- stripe.com Atlas for incorporation
- vercel.com for easy frontend deploys
- render.com for easy backend deploys
- pilot.com for taxes
- stripe.com for subscriptions
- sendgrid.com for email
- orbit.love is good for tracking community growth
- discord for actual community conversation
- best marketing for us has been word-of-mouth, thankfully driven just by doubling down on product and support
I think Vercel and Render are both doing an incredible job with their deploy processes, and a lot is owed to them not requiring use of a CLI. I wonder if I'm in the minority here?
A portion of our customers will ask us about NIST, but it's slimmer than I would have expected.
Here's the direct link to NIST 800-63B - it's really a fantastic document with sensible recommendations on every authentication method: https://pages.nist.gov/800-63-3/sp800-63b.html
The tedious part of NIST's password requirements is "Do check for compromised passwords"
HaveIBeenPwned exists, but most open source tools don't leverage it and this requirement goes overlooked.
At Clerk, we follow NIST guidelines by default, including integration with HIBP. In a world with password reuse and "credential stuffing" attacks, this feature is critical to securing your user accounts (unless you go full passwordless, but that has its own tradeoffs).
Indeed, we're still catching up on feature-completeness, especially for the B2B world. In 2022 we will be tackling organization onboarding, SAML SSO, roles and permissions, subscription management, and authorization (as it relates to permissions and subscriptions). Still just getting started...
In general, we're pushing for more use of short-lived JWTs to sync identities between trusted parties instead of OAuth. We think this is preferable because it can keep sessions synced, while OAuth tends to result in independent sessions.
Admittedly, we do not run an OpenID Connect server on behalf of our customers yet. This is on our roadmap and will be added soon, and there are definitely some services that we can't offer first-class integrations with until that happens.
Your other point about "every language has an OAuth framework" is well-taken. Our initial launch used something proprietary and this was an issue, but we recently switched to spec-compliant JWTs to benefit more from existing libraries. Here's an example of our current, standard token:
https://jwt.io/#token=eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18xcn...
I'm interested in why FusionAuth chose OAuth to share identity between localhost:${serverPort} and localhost:${fusionAuthPort}?
Auth0 does the same, and it's always struck me as misplaced. OAuth makes sense to me between two distinct parties, but the architecture here treats FusionAuth and the application as a single party. Meaning, when a user signs in through the FusionAuth form, they're actually signing in to the application.
That's not true of Google OAuth - if a user signs into Google, they're not automatically signed into every service where they've ever clicked "Sign in with Google".
We moved away from this model because it also makes it really hard for FusionAuth to manage the user's session on the developer's behalf. You can see in this writeup that the developer is asked to pull in express-session to manage the user's session on their own.
Our mindset is: since Clerk is same-site to the developer's server, we can manage the user's session on behalf of the developer. When dev's use clerk, they don't need to think about cookies or sessions at all, except to the extent that lifetimes are configurable in our dashboard.
Those updates broke a lot of websites - you might have noticed SSO failing, iframes failing, or websites confusing unmodified Safari users for adblock users.
Firefox adopted the changes a bit later. By the time they made the switch, I think most had resolved their sites because of Safari. Last I read, Chrome is slated to implement similar for 2022.
Not intended to be a defense of Safari, especially for OP's bug. But at least some breaking changes were deliberate and in the name of privacy enhancements.
With SSR today, developers need to take a `request` object and preload all the data necessary to render the page _before_ any component logic runs.
This is a huge pain because React components often have conditional branches inside them. Developers are forced to predict which branches need to have data preloaded, and then non-trivially pass the preloaded data into the component tree.
Look at how useSWR (a popular data fetching library) supports SSR...you preload the data and pass it in as "fallback" data. Notably, the fallback data is loaded BEFORE React starts running in a server-rendered environment. That's because React doesn't allow asynchronous actions during server-rendering - it must be available before React computes the initial HTML:
https://swr.vercel.app/docs/with-nextjs#pre-rendering-with-d...
With React's new <Suspense>, the ugly SSR ergonomics disappear. Suspense allows data to be awaited during rendering, instead of it needing to be preloaded out-of-band.
This means that code for data loading can be contained completely within the relevant component, and not also duplicated within SSR preload functions.
(It also kills the primary use-case for useEffect(), which IMO is the least ergonomic hook.)
What's interesting is that <Suspense> solves ergonomics issues in both server- and client- environments - which muddies the answer to your question a bit...
Server Components are guaranteed to become the standard for developers who need SSR (ecommerce is certainly one use-case, since they want SEO).
Whether Server Components _also_ become the standard over Client Components remains to be seen. But that's purely a question of performance, not ergonomics. The decision may end up completely abstracted away from the developer via something like Next.js.
The consensus we reached is that stateless auth is good, but the amount of development required for _secure_ stateless auth is prohibitive.
We built our session management service to make stateless and revocable auth incredibly easy to implement. The tokens are short-lived and we handle all the refresh automatically with our SDKs. A real-time demo of that is available here: https://edge.clerk.app
If your company could benefit from shaving some milliseconds off your authentication check, or if you'd like to add device management and revocation to your application - we'd love to chat!
Today our session management product is still coupled with our user management product, but that will change soon!
The hard work of generating the JWT is done in the background, out of the critical path, so the request can just fly through verification.
The debate of stateless JWTs vs stateful auth is well-storied on HN, and we frequently checked this thread to make sure we were checking all the boxes: https://news.ycombinator.com/item?id=21783303
We think stateless JWTs are technically better (e.g. much faster with more-than-acceptable security for most use cases), but it's always been too big of a hassle to setup the short-lifetime that's necessary for security.
So we endeavored to build a product around "stateless auth done right." We handle all the refresh mechanisms for you - it's completely abstracted away. If the 60 second lifetime is too long for you, we still provide an easy way to drop down to stateful auth against our API.
At present, this authentication mechanism is strictly bundled with our User Management product that includes sign-up and sign-in flows and a user profile page. One thing we're interested to learn: is there any demand for a standalone "session management" product where you bring your own user management solution?
Thanks!
For example, Stripe offers a client-only integration with similar limitations - the product catalog must be defined within the stripe dashboard. https://stripe.com/docs/payments/checkout/client
At https://clerk.dev (disc: founder), we do all of sign up, sign in, and user profile management from just the client. We’re able to keep it secure because we also do session/cookie management, so can tell who is signed in.
Emailjs has an interesting concept here. It’s definitely on-trend, though I personally wouldn’t like users being able to trigger emails to other users, which seems possible today (correct me if I’m wrong?)
Have you considered an option to require a signed JWT with requests, which could contain the user’s email address? That would make it so users can only trigger spam to themselves (except in cases of xss), but retain a mostly client-only experience. (Generating the jwt requires a backend, but developers may already have one available if they’re using another service like Hasura that follows a similar pattern.)
This is a really fantastic point, and it applies to CORS as well.
It's common to ditch CSRF tokens when CORS is configured, which is perfectly legitimate. But reflecting the Origin in Access-Control-Allow-Origin is dangerous if you have subdomains hosted by a third party AND are using `credentials: "include"`.
It's crazy to me that HTTP servers need to rely on tricks like this to understand the context of a request.
At a minimum, it would be nice if `Origin` was sent with all requests from browsers, not just cross-origin XHR requests.
I think what OP has glazed over a little bit, is that authentication is a massive, thorny exception to their advice of "adopt the Same Origin Policy instead."
Consider these authentication use cases:
- Sharing sessions across mail.google.com and drive.google.com
- Sharing sessions across google.com and youtube.com
- An okta.com generating a session on google.com
- Persisting a youtube.com session even when youtube.com is in an iframe on example.com
All of these scenarios are reasonable, but it's a huge challenge to support all of them in a privacy- and security-conscious way.
That being said, I generally agree with the advice of "adopt the Same Origin Policy instead" after authentication is solved.
We're excited for other factors to gain more prominence (e.g TOTP, face id, touch id), but in the current world it's hard to recommend against a password factor, as long as you're following NIST guidelines to compare against password breach corpuses.
We put a hidden field on the page to accept a password from a password manager. If the password manager fills it, we expose the field to indicate the password was received.
---
The second page is helpful for users who originally sign up with Facebook/Google, and therefore don't have a password to enter on the first page. (It's also helpful for SAML, but we don't support SAML quite yet.)
Edit to add: Even if we had email/password on one page for everyone, I would still expect Social Sign In to win.
We don't actually have telemetry on password manager vs keyed, but the way things are distributed we can make an educated guess. I'd guess that PW manager users have a chance at beating Social Sign In users, but too many are still keying their passwords for passwords to win overall.
Thanks for another blog idea :)!