If I'm not mistaken, this is written by the author of Lucia, a popular auth library for TypeScript [0]. He recently announced that he will be deprecating the library and be replacing it with a series of written guides [1], as he no longer feels that the Lucia library is an ergonomic way of implementing auth. He posted an early preview of the written guide [2] which I found enjoyable to read and complements The Copenhagen Book nicely.
[0] https://github.com/lucia-auth/lucia
> he no longer feels that the Lucia library is an ergonomic way of implementing auth
has he written up why? lots to learn here
edit: oh: https://github.com/lucia-auth/lucia/discussions/1707
this is great. he saw the coming complexity explosion, that the library was no longer useful to him personally, and took the humble route to opt out of the Standard Model of library slop development. rare.
Yeah, but Lucia is just going to be immediately replaced with some other popular auth library.
The thing is, 99% of people really do just need 'log in / log out', and this is an incredibly useful thing to have as a library.
If you need Web 8.0 passkeys served via WASM eliptic curve sockets or whatever, sure, roll your own or use Auth0. But it feels really silly for the consensus around auth to be 'oh, you're making a CRUD cooking app to share your love of baking? cool, well here's the OAuth spec and a list of footguns, go roll some auth'. It's not a good use of that person's time - they should be focussed on their actual idea rather than being forced to reinvent plumbing - and tons of people are going to get it wrong and end up with effectively no auth at all.
Been rolling my own auth today for luls. Thanks for this :)
This is wrong:
> Email addresses are case-insensitive.
From https://thecopenhagenbook.com/email-verification
The email standard says they are case sensitive.
If you lowercase emails during send operations, the wrong person may get the email. That's bad for auth.
Some (many) popular email providers choose to offer only case-insensitive emails. But a website about general auth should recommend the general case.
https://stackoverflow.com/questions/9807909/are-email-addres...
Side remark: It is not always clear/obvious what's the other case of a given character is, and may change over time. For example, the German capital ß was added in 2008 to Unicode. So it's best to avoid case sensitivity where you can, in general programming.
The top stackoverflow answer you link to disagrees with you: "In practice though, no widely used mail systems distinguish different addresses based on case."
The standard says one thing, yet implementers do another. In this case following the letter of the standard gets you in trouble in the real world.
My practice on this is to store the user-provided case, but do case insensitive lookups.
This means that you send emails to the case-sensitive address originally entered, but the user is free to login case insensitively.
The downside is that you cannot have two distinct users with emails that only differ in their case. But I feel rather OK about that.
I had case where you could register with your email and the email then became your username which was used to login. The email was case insensitive but the username created from the email was case sensitive, if you created the account with uppercase capital letter email, that was your username and case insensitive email didn’t work to login.
Let’s abolish capitalization altogether. Such a waste. And time zones next. UTC FTW! Then we can do away with languages, and cultural differences. Gazillions of LOC down the drain. I can hear the repositories shrinking already
While in theory it's true, in practice I've seen multiple systems that due to early implementation bugs they had multiple cases for the same email, which obviously were from the same person, think JohnDoe@example.com on one user entry and johndoe@example.com in another user entry. These were also coming from different systems, which made it all even more troublesome.
I would argue the risk matrix of having case-insensitive emails looks much better than the risk matrix of having case-sensitive emails (meaning, you should lowercase all the emails and thus The Copenhagen Book is right, once again).
It's worth noting that most reputable transaction email services only accept ASCII characters in email addresses so it's at least worth notifying the user that non ASCII emails are not allowed.
In most cases an accented character is a typo. If you have a non ASCII email I guess you are used to pain on the internet.
Wow, this is very nice. One of my pet peeves is how 90% of security resources seem designed to be absolutely inscrutable by non-security experts - especially anything from cryptography. Every single page in here however is clear, concise, to the point, and actionable, love it! (except the one on elliptic curves, which I find about as incomprehensible as most crypto resources).
Just a small comment/opinion on the inscrutability of crypto:
Crypto relies on number theory and a complexity theoretical assumption that N!=NP (i.e. that there exists one-way/trapdoor functions).
I think it is opaque by the very nature of how it works (math).
Understanding finite fields or elliptic curves (integer groups really) made me able to grok a lot of crypto. It is often a form of the discrete-logarithm problem somehow.
Would be nice to see alternative documents for similar topics (e.g. something like OWASP Cheatsheet but from more practical point of view).
With all the respect, I'm a bit skeptical about this document for such reasons:
- Name is quite pompous. It's a very good marketing trick: calling some document like if it was written by group of researchers from a Copenhagen university. :)
Yes, Lucia is a relatively popular library but it doesn't mean that it is promoting best practices and that its author should be considered an authority in such important field unless opposite is proven.
- I don't like some aspects of Lucia library design: when user token is almost expired - instead of generating new security token Lucia suggesting just to extend life of existing one. I see it as a very insecure behavior: token lives forever and can be abused forever. This violates one of the security best practices of limited token lifetime.
But both Lucia and "Copenhagen Book" encourages this practice [1]:
``` if time.Now().After(session.expiresAt.Sub(sessionExpiresIn / 2)) { session.ExpiresAt = time.Now().Add( updateSessionExpiration(session.Id, session.ExpiresAt) } ```
[1]: https://thecopenhagenbook.com/sessions#session-lifetime
> when user token is almost expired - instead of generating new security token Lucia suggesting just to extend life of existing one
The link you posted shows code to extend the session, which is common practice (it's called rolling session), not to "extend" the token's life (which should be impossible, a token needs to be immutable in the first place, which is why refreshing a token gives you a new token instead of mutating the original).
If you destroyed a token and send a reply to the client with a new token, but the client already sent you a new request with old token, that request will be denied.
I don't think there's any difference between extending the existing session and creating a new one in Lucia's context.
In the first scenario, the attacker steals the token and uses it forever. In the second, the attacker steals the token and they'll get a fresh one near its expiry. They can still impersonate the user forever. The user might notice something when they get kicked out (because the attacker renewed the token, rendering the old one invalid) but it's unlikely. For good UX, you need a grace period anyway, otherwise legitimate users can have problems with parallel requests (request one causes a token refresh, request two gets rejected because it was initiated before the first one was completed).
You can use a second token (a refresh token) but it only pushes the risk to the second token. Now we need to worry about the second token being stolen and abused forever.
Refresh tokens are useful for not having to hit the database on every request though: Typically, the short lived session token can be validated without hitting the db (e.g. it's a signed JWT). But it means that you can't invalidate it when stolen, it will be valid until expiry time so the expiry time has to be short to limit the damage. For the refresh token, on the other hand, you do hit the db. Using a second token doesn't add any security, hitting the db does, because the refresh token can be invalidated (by deleting it from the db).
Lucia always hits the db (at least in their examples), so you can invalidate tokens anytime. To mitigate risks, you can allow the user to see and terminate their active sessions (preferably with time, location, and device info: "Logged in 12 AM yesterday from an iPhone in Copenhagen"). You could also notify the user when someone logs in from a new location or device.
That's about all you can do. There's simply no fully secure way of implementing long-lived sessions.
I think you’re reading into the name a little, haha. I’m interested in your alternative method for session token replacement, though! I think you make a good point, but I’m not an expert by any means.
There are two things that everybody misses about OAuth and they fly under the radar.
Nice to hear someone touch on one of them: you absolutely NEED to use a transaction as a distributed locking mechanism when you use a token.
This goes double/quadruple for refresh tokens. Use the same token more than once, and that user is now signed out.
It doesn't matter if your system runs on one machine or N machines; if you have more than one request with a refresh token attached in flight at once - happens all the time - you are signing out users, often via 500.
Refresh tokens are one-time use.
The other thing devs and auth frameworks miss is the "state" parameter.
There is no "one-time" over the network. Invalidating the refresh token immediately when the server recieves it is asking for trouble.
Too complicated, not suitable for everyday authentication in my opinion.
Seriously, the way auth in general is developing right now, I think we approach a point of insecurity through obscurity.
And with applications states, you need to adapt the application logic to authentication and the application then would have to check if someone maybe stole your refresh token.
Most systems implement a grace period for refresh token reuse for similar reasons. Transactions don’t really solve it. (Ex: You open two tabs quickly, hitting the server with the original refresh token twice)
You are probably familiar with a document called OAuth Threat model.
In that document, refresh token rotation is preferred, but it also addresses the obvious difficulty in clustered environments: https://datatracker.ietf.org/doc/html/rfc6819#section-5.2.2....
Sorry, but that doesn't work in real world applications. Multiple requests are fired simultaneously all the time. E. g. browsers starting with multiple tabs, smartphone apps starting and firing multiple requests etc.
> The other thing devs and auth frameworks miss is the "state" parameter.
What do you mean?
I wish more websites would grant you the option to say "I never want my session to expire until I log out, I understand the risks". The "remember me" button does nothing these days.
I'm so tired of having my day constantly interrupted by expiring sessions. GitHub is my least favourite; I use it ~weekly, so my sessions always expire, and they forced me to use 2FA so I have to drag my phone out and punch in random numbers. Every single time.
As well as being terrible UX, though I have no evidence to back this up, I'm pretty sure this constant logging in fatigues users enough to where they stop paying attention. If you log into a site multiple times a week, it's easy for a phishing site to slip into your 60th login. Conversely if you've got an account that you never need to log into, it's going to feel really weird and heighten your awareness if it suddenly does ask for a password.
Regardless, companies should learn that everyone has a different risk appetite and security posture, and provide options.
Side-note, Github's constant session expiry & 2FA annoyed me so much that I moved to Gitea and disabled expiry. That was 90% of the reason I moved. It's only available on my network too, so if anything I feel I gained on security. Companies 100% can lose customers by having an inflexible security model.
This resource has a good recommendation on how session lifetimes should work.
Roughly, that it should continue for 30 days if used within 30 days.
Notion is the worst offender for me, not because of its frequency but because it will actually interrupt an active session and forces you to log back in. Worse, the session seems to last just a minute or two longer than one week, which means it will boot you this week just a little bit after you started your work last week.
A few weeks back I logged in right before a recurring meeting to take notes, and for several weeks running it's been interrupting me in the middle of that meeting to force me to log in again.
Are you sure you haven't toggled something in your browser settings? There are a plethora of settings that would wind up rendering those "remember me" buttons useless. Anecdotally, I haven't had any issues with staying logged into websites. Github being one of them.
Some authentication providers cap token lifetime depending on pricing plan.
At least you can enjoy security theater.
Um, my session with GitHub never expires - weird.
[flagged]
Nice.
I recently learned about the SRP protocol [1], and I’m surprised that it’s not more widely used/mentioned: with a relatively simple protocol, you can do a ZKP and generate a session token between the server and client in one fell swoop.
[1]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protoco...
Just chiming in that I appreciate this resource. A lot of security advice is esoteric and sometimes feels ridiculous. Like a lawyer who advises you not to do anything ever. This guide was refreshingly concise, easy to follow and understand, and has good straight forward advice.
I'll keep an eye on these comments to see if there are any dissenting opinions or caveats but I know I'll be reviewing this against my own auth projects.
One thing I would like to see would be a section on JWT, even if it is just for them to say "don't use them" if that is their opinion.
By “auth” do they mean “authn” (authentication) or “authz” (authorization)?
It looks like they mean authentication but it would be nice if they were clear.
Somewhat related, I have a short rant about embedded browsers killing the web.
Embedded browsers make it impossible (literally in some cases, figuratively in others) to use social OAuth. If you click a link on Instagram, which by default opens in Instagram's browser, and that link has "Sign in with Google", it simply will not work, because Google blocks "insecure browsers", which Instagram is one. There are even issues getting "Sign in with Facebook" to work, and Meta owns Instagram and Facebook! The Facebook embedded browser suffers from similar issues.
What is the rationale behind the following?
> When comparing password hashes, use constant time comparison instead of ==.
If you were comparing plaintext you'd get some info, but it seems overly cautious when comparing salted hashes. Maybe anticipating an unknown vulnerability in the hash function?
Good stuff. The model of "how to build" vs. "library that does" is a good idea when there's combinatorial explosion and you want to reduce the design space.
At a previous employer, people built some tool that auto-built Kube manifests and so on. To be honest, I much preferred near raw manifests. They were sufficient and the tool actually added a larger bug space and its own YAML DSL.
Anyone know why it's called the Copenhagen Book?
I was just reading this and was gonna recommend it to a friend! Saw the announcement from Lucia moving to a resource-based repo and digging deeper saw The Copenhagen Book, which IMHO is the best resource Auth-related I've seen in my 10+ years of career, all very well put together.
Two tradeoffs I see is that it is a bit abstract, and also a bit brief/succinct in some places where it just says it as it is and not the why. But neither of those are really negatives on my book, just concessions you have to make when doing a project like this. You can dig deeper in any topic, and nowadays libraries have pretty good practical setups, so as a place where it is all bound together as a single learning resource is AMAZING. I'm even thinking of editing it and printing it!
> CSRF protection must be implemented when using cookies, and using the SameSite flag is not sufficient.
Also when it's set to strict? Or if it requires a PUT or other method that doesn't work with top-level navigation? Is it about ancient or obscure browsers that didn't/don't implement it (https://caniuse.com/same-site-cookie-attribute)?
This looks ver useful if you're about to implement an Auth system. But I thinks it's worth noting that many things can be offered without authentication, i.e. without an account. I think it's worth noting that e.g. e-commerce can be (and in some rare but appreciated cases is) offered in "guest mode". Especially for smaller or more niche shops where return customers are less frequent, it's just good to keep that in mind.
It’s nice to see something other than “don’t roll your own, it’s dangerous.”
I especially appreciated the note that while UUIDv4 has a lot of entropy, it’s not guaranteed to be cryptographically secure per the spec. Does it matter? For nearly all applications, probably not, but people should be aware of it.
It doesn't talk about ID tokens and JWT etc with the API only security/use case?
you only need to roll your own auth once and you can drop it in anywhere
I wonder why they recommend hashing server tokens in some cases. Is it so that someone who can read the database can’t hijack an account? Or am I misunderstanding why hashing is used?
Well, web applications are not web sites, as the latest would support authentification for noscript/basic (x)html browsers.
This is a great guide, thanks.
Great guide, thank you.
- Passwords must be at least 8 characters long....
- Use libraries like zxcvbn to check for weak passwords.
These rules might be good for high-security sites, but it's really annoying to me when I have to generate a length-15 string password with special characters and uppercase for some random one-off account that I use to buy a plane ticket or get reimbursed for a contact lens purchase.
i see that most examples are implemented in golang, if possible would also request the author to consider adding python and node.js snippets
What's with the name?
[dead]
[flagged]
[flagged]
[flagged]
I like it but and I know it’s nit picky- is there a off or other book like thing one can “hold”
If you're doing auth in 2024, please consider not supporting passwords. Most people will never use a password manager, and even if they did it's not as secure as key-based approaches or OAuth2.
Obviously there are exceptions