sjudson / paseto.js

PASETO: Platform-Agnostic Security Tokens
MIT License
269 stars 16 forks source link

logout - delete/remove or make token invalid again #30

Closed zanzara closed 3 years ago

zanzara commented 3 years ago

I want to make an logout and therefor I want to set the token somehow to invalid or remove it, but i really don't know how to do it? apreciating any hints.

What stands "decapsulate" module for?

Extarys commented 3 years ago

You can use the jti claim (which is a random ID you set when the token is created) then you would need to compare that token id to the database on every request - if you only use 1 token.

If you also have a refresh token: remove the paseto cookie and refresh token on the client-side when the user log out, and make the refresh token invalid on the server. I have a refreshtoken table in my database with a list of jti. I would simply remove that row from the database.

zanzara commented 3 years ago

Thanks. Could you provide an example/codesnippet? I using this paseto implementation from github:stefanvanherwijnen/paseto.js and I really don't know to do it there. I use only one claim with the id of the user. I got obviously no cookies set in postman. What does jti means here? Sry. for that much newbie question, but I thought it would be somehow easier, afterwards "login' and auth check worked with this one from stefanvanherwijnen/paseto.js

zanzara commented 3 years ago

Edit: Meanwhile I could dig through this "passwordForgot()" pattern in stefanvanherwijnen/paseto.js and I use now for logut the same item "tokens_revoked_at" and set it in the databse to now(). My problem was in the first try, thate I assigned tokens_revoked_at: new Date().toISOString() which is not a good idea while using MySLQ (MariaDB). Because apparently some daylight or TZ conversions are done.

So I assign it as a plain new Date() and now revoke logic fits while auth check.

Extarys commented 3 years ago

I would suggest you open a StackOverflow question about that, explaining what you are currently doing and what you wish to achieve. Many programmers farm points there ;) I'm sure someone with more experience than me will be able to give you a more detailed answer.

You can look at some JWT implementation and posts on the web and just apply that knowledge to PASETO. JTI is a claim on the token to give it a unique ID. Just generate some crypto secure bytes and covert them to hex for example.

JWT claims from W3C

This is the essential of it, research and test to make sure your implementation is safe. I don't use the package you are referring too (I'll take a look soon though as I'm curious)

Access token contains: issued at (iat), expires at (exo) (15-60 minutes), userid, role/permission, any properties you dont want to look into the db on every request. Make properties as short as possible as the token will get huge quickly. Also when using refresh token, make the access token short lived. Note that you'll need to make a database request evey time the token expires, for each user.

Refresh token: jti, issued_at, expires_at (7 days?)

When the user logs in, send the access token with a refresh token. DO NOT store those tokens in local storage, use HttpOnly and SSL cookies option for those two. Send with it the expiration of the refresh token and the access token.

jti issued_at expires_at was_revoked ip replaced_by
4d9a5h41b3ac97eff454a 2021-01-01 10:50:50 2021-01-07 10:50:50 false 127.0.0.1 NULL

(EDIT: I hate markdown tables)

When you log out the user, either remove the row or mark it as revoked. It's good to keep some history you can display the user last connections, active devices, etc.

When the access token expires, the client sends the refresh token to the server and it get a new access token (if the refresh token wasn't expired/revoked). If the server receive the refresh token and it's about to expire, you can either request the user to log in again or just renew the refresh token and saving the new refresh token in a row of the DB, and updating replaced_by on the old one, again, to keep track/history of the devices etc.

If the refresh token is expired (meaning the device didn't login/was used in the last 7 days, ask the user to log in again.

When the user logs out, clear the access token and the refresh token cookies and mark the refresh token as revoked.

Also set a cronjob to clean the database >3 months old tokens for example, depending on your implementation approach.

Rate limit the login and refresh endpoint (per ip and/or username/email) and monitor failed login attempts to protect the accounts as the api can be abused!

sjudson commented 3 years ago

@zanzara Things like logout are part of the authorization/authentication scheme, not the token format. So this question generally lies outside the scope of this library, hence why I am closing it.

@Extarys has some reasonable (but pretty opinionated, and not for all use cases, imo) advice if you're using a paseto as an access token within OAuth 2.0/OpenID Connect (esp. when using a JWT format) or for session management. But the general principle pretty much always holds -- either stick the jti (if JWT) or the whole token into a blacklist in your database and include a check against it. For OAuth 2.0 the resource server can always check for logouts by querying any token it receives against an appropriate endpoint exposed by the authorization server (this is "validation" is mentioned in the OAuth 2.0 spec, see RFC 6749 Sec. 7 [pg 41]). OpenID Connect has its own specs for how to handle and propagate logout throughout the federation, whether starting directly at the authorization server or initiated by some client (RP, in their terminology). In general handling logout is a rich topic in these sorts of systems, and I recommend you look into some of the links I've given you and do some research. But blacklisting is generally how all of this stuff works at the token-level.

Extarys commented 3 years ago

Hey @sjudson thanks for stopping by. I kind of glued together multiple sources and how-to into a small document for reference and this is how I use PASETO without having a full fledge oauth server. Do you have more links that can help me understand other ways to implement this? JWT or PASETO, although PASETO implementations seems harder to find, hence my limited/opinionated implementation.

sjudson commented 3 years ago

@Extarys what do you mean "without having a full fledge[d] oauth server"?

Generally speaking if you're just shoving a paseto or jwt into a cookie to maintain the session w/o having to hit a session store, then blacklisting tokens removes the benefit of using them in the first place. But usually in that kinda simplistic case you can just wipe the token from the cookie and call it a day -- sure the user can restore the cookie, but it's not like they should be surprised their session comes back to life when they do so. If your service does anything that depends in a meaningful way on the (non)-existence of a session though then this "just delete the cookie" may not be robust/secure, whereas blacklisting will be.

If you're in an environment where you have client and/or resource server resources (whether OAuth 2.0/OpenID Connect or anything else), then the main questions around logout that come to mind are (i) who is the authority as to whether the user is logged out?; (ii) should the user being logged out at that authority then force logout/deauthorization to the various clients/resources;? (iii) if so, are those latter logouts to be achieved by a push from the authority or by a query from the client/resource?; and (iv) in both cases, how do you do that robustly, i.e., how do you make sure the query is made and/or the push is received and processed? Then you have to make sure that your implementation is secure, which in particular along with robustness means you can't be depending on a potentially malicious user/user-agent to behave a certain way/carry certain messages for you as part of the logout process.