Hacker News new | ask | show | jobs
by barrystaes 3070 days ago
Do everything via HTTPS, disable HTTP. The login request (POST, dont use url query params) contains username + password. The API replies with a session token (a random string). You can store any metadata relating to this session token in your DB.

The API client should this token in every request that requires authentication, often in the header as `Authorization : Bearer 123TheToken456`.

JWT: If DB performance becomes a problem (or you want to expose signed session metadata) consider using JWT to provide session validation with the request itself. The downsides of JWT are that its often used to hold secret values (dont do this), or is a few kilobytes big which makes all requests slow, or stupid mistakes in signing and session validation that make it very insecure like allowing any request to just specify false permissions.

5 comments

I have found the above (sans JWT) to be the simplest, secure method. Do everything over HTTPS, use basic auth or post for the user/pass and return an expiring token, use that token as a Bearer token for all subsequent requests.
It's generally a good idea to avoid JWT. There are a lot of foot-guns in JWT, and many implementations have gotten it wrong in the past. This[1] is a good summary on the topic.

[1]: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...

On the client side that adds some complexity. You either do always two requests (first get token, then use it) or need to manage the storing the token and doing the renewals. If there are bursts of traffic and multiple threads/processes you need to think if each one will get their own token or if others will wait while one is getting a fresh token.
I’m mostley a FE person at the moment so forgive me for my ignorance but how does the server use the token passed to the client to do auth?

Does it keep a copy somewhere and check against it on every request?

> Does it keep a copy somewhere and check against it on every request?

Yes. The last time I did this we checked the X-Token header on every request, if it didn't exist or there were multiple we replied 401. If only one was there we checked a DB table of active tokens, if it wasn't there or had expired we replied 401. If it was there but wasn't associated with a role that had access to the requested resource, we replied 403. If it was not expired and had access we continued with the request.

As soon as you get away from "check authentication on every request" your attack vectors increase. As a bad actor, I no longer need to bypass your authentication, I just need to bypass whatever system you have in place to decide whether or not to authenticate me. That's generally going to be easier.

I was planning to use custom headers over HTTPS for a similar thing, but then I read that some firewalls will strip custom headers, even over HTTPS. Can anyone with experience comment? Thanks.
Just use a Bearer token in the Authorization header from the OAuth spec: https://tools.ietf.org/html/rfc6750#section-2.1
> Does it keep a copy somewhere and check against it on every request?

Yep, a store like Redis that has automatic row expiration is what I have used in the past. Most clients will often be bursty in their requests so a simple few second cache on API after verifying the token can also be useful/performant.

Yes, a copy is kept somewhere — that somewhere could be application memory, a local file or database on the server where the application is running, or another server that provides this as a service (which may in turn store in memory or disk/database). Which approach is chosen depends on factors like performance/load capacity, availability/redundancy requirements (answering questions like, "if a server goes down, will users be logged out?"), etc.
I basically do this with jwt. In my case jwt just contains the basic data that the front needs to find out who the user is and what it can do (user uuid and role). While obviously checking if action is allowed to user is done server side it's normally useful for the front end to also be aware.
Yea, I happen to be using JWT in the simplest way. Authentication only.

I don't even store role information in them, since authorization checks are performed on the server anyway.

If the client needs to know what a user is allowed to do with a resource (so it knows not to display certain buttons, etc.) I have the client do an OPTIONS call (with the token) to see what methods are allowed.

Lately, I've been thinking about replacing the whole JWT scheme with simple bearer tokens stored in the database, mostly because it would make revocation simple and I can't think of anything I would lose by giving up JWTs (a little storage space in the database?), and I don't think switching the type of bearer token I'm working with will actually be very painful implementation-wise. You know what, I'm adding a task to my backlog...

Doesn't a session token violate the stateless principle ?
Yes, it does.

If you want to be completely stateless, you need to send the authorization info on each request, probably as basic auth headers.

This is actually simpler than implementing the Bearer token, for both client and server, but requires that the client retain the non-expiring username and password, rather than retaining the expiring, session-specific Bearer token.

> but requires that the client retain the non-expiring username and password

IMO, this makes this simpler solution a non-starter. It becomes way too easy to leak credentials.

The auth framework I use avoids this problem but remains stateless by encrypting/signing the tuple (user ID, session expiration time, maybe other stuff) in a cookie. Essentially it's using the browser as an encrypted one-row database to store the info that would normally be in a sessions table.
This can work, but you want to keep what you send with every request very small. It's also hard to do a mass expiration or revoke a single session. If you have the tokens on the server you can run a query and easily do both. Checking signatures and decrypting on every request can also be a performance issue.
How so? The client needs to have the username and password to call the login function over and over, as well as managing the session token. It's just incredibly annoying with no benefits in nearly every scenario.
The client should not store the user/pass [1]. If the token expires, the user needs to provide a user/pass to login in again. The user should also be forced to provide a user/pass in order to change the password - something that cannot be enforced if keeping the user/pass on the client.

You also lose any method to force a re-authentication. With a token, I could expire with no activity for an hour and allow it to be good for a max of 2 hours.

[1] Users have a bad habit of just leaving computers. With a token, the worst case is someone has a short lived access to to something. With a user/pass left on the client worst case now becomes use/pass taken.

What is the client? OP says API which sounds more machine to machine. If you mean the API powering a site, used from the client's browser, then sure. Track separate logins, then give them a control panel to see where they're logged in.

But most clients store their user/pass in their browser anyways so I'm not sure it's a security win for preventing credential loss.

You don't lose re-auth. The master system issuing API keys can revoke keys, too.

But anyways maybe we're talking about different contexts because I don't understand the scenario you're describing.

Yes, if you mean a token that identifies a session that is saved on the server somehow. But not a JWT, which is only saved on the client and verified on the server.
"Stateless" refers to the app, not to its datastores, because that would be nonsensical.

JWT is also poorly specified (no protocol under any circumstances should use negotiation, it does not support revocation, and it has been hammered home by the best security folks I know that public key cryptography is what you do when you don't have any other choice) and dangerous to use. Avoid it. Do the simplest thing that can possibly work. That's a session token. If, in the far and unlikely future, you are so successful that a single database call is so harmful, then you have the money to hire someone who doesn't have to Ask HN this question.

Totally agree -- but verifying a session token doesn't even have to be a database call. Put the token in memcache or redis; now, if you are so successful that a single network call that doesn't touch disk is your bottleneck, well, you can hire some very smart people to fix that for you.
If you use it for authorization only, it doesn’t.
What is your reasoning for not using query params for the login request? I know it's probably more RESTful to use POST, but otherwise if you're using HTTPS for everything, query params are just as encrypted as the POST body. Or is there another reason?
For one, the query string is much more likely to be logged, compared to the entity body. Think: httpd access logs, browser history, misconfigured caches / correctly configured caches subject to inappropriate cache directives, etc.
GET requests just do not make sense for actions: they are cacheable and replayable. An http client/a proxy/something on the backend can cache it and avoid going to the actual logic.

Also, mixing credentials into URL does not feel like a good separation of concerns, e.g. URLs are often logged and analyzed in separate logging/monitoring/analytic tools, so there is a bigger risk to have credentials leaked over some side-channel.

Query params in the URL are encrypted for transmission, but not elsewhere: http://blog.httpwatch.com/2009/02/20/how-secure-are-query-st...
Is this "go code this yourself" advice, or just what to look for from pre-existing tools? In my world there are middlewares that accomplish exactly this, it never occurred to me to code this myself, lest I screw it up.
Sure, the auth middlewares out there like Passport for js or Spring security on the java side do most of the work. You generally still have to code the very last bits of checking if the user/pass is good, generating the token, and checking if the token is good. There may be ones out there that do everything, but they are typically not quite as flexible as I'd like (lets store the token in Redis instead of an RDMS for example).