I'm new to JWT, but it looks better than just using the same API token forever.
I know the token expires after a day (by default), but does that mean the user will have to re-login again?
How does auth work between multiple devices? (esp in the case of a refresh token)
Does this support automatic refresh tokens?
No, Knock does not support refresh tokens out of the box. I never felt the need for it and I believe that adding long-lived refresh tokens would add extra security concerns and diminish the simplicity inherent to using JWT.
I know the token expires after a day (by default), but does that mean the user will have to re-login again?
Yes, the user will have to login again after a token expires.
this could lead to some un-desired behavior if someone's token is about to expire when they are in the middle of work.
I understand the reason why you would want to implement something like a refresh token. Although I think there's other ways to address this kind of issues. Consider sliding sessions for example.
With sliding sessions, you would send a new short-lived token with every authenticated action made by the user. As long as the user is active he will stay authenticated. If the user sends an expired token, it means he has been inactive for a while.
How does auth work between multiple devices? (esp in the case of a refresh token)
There's nothing special about multiple devices authentication. I don't have an answer in the case of refresh tokens in particular, because I did not implement it.
I hope this answers your questions. I'm not saying I'm entirely close-minded to refresh tokens if it's something that becomes unavoidable in the future. But I don't believe it is the case at the moment.
Documenting sliding sessions in the README and making sure it is easily implementable would be a good actionable for this issue.
Thanks for your feedback 馃憤
Yeah, so thanks for all of this! this is all wonderful information!
I never considered that implementation of sliding sessions, and I think that would be the ideal way to do things.
For sliding sessions, does the server then have to keep track of the session (making auth no longer stateless?), or is that something auth0 could provide?
Right now, for my app, I'm managing tokens myself, and have been looking at auth0 + jwt as an alternative, I really want to understand all this before dumping a bunch of time in to implementing (which would still probably be less time than hacking on Devise)
No, this would still be entirely stateless. No need to keep the token server-side.
e.g.
{
"resource": { ... },
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
}
If the tokens are generated with a 24h expiration, then the user will only need to re-enter his password after being inactive for a day.
I believe the beauty of JWT is the simplicity both of the concept and implementation. I encourage you to try it on a small dummy application to understand it better if you need to.
If you can afford it, Auth0 is also a great solution if you want to decouple authentication entirely from your app.
Happy I could help! :)
Closing this for now, but feel free to comment back on this issue to tell us how you went with this! 馃憤
I'm new to JWT but most of this is making sense to me. The only concept I'd love to see some example code for is in regards to creating a new sliding session token with Knock. I'm comfortable with the concept of sliding sessions. However, I'm unclear where Knock would typically tie into the Rails response cycle and how it can create a new sliding session token to send back to the client. I see in the README that we can modify the token payload but I'm not sure if that's how sliding session tokens are handled. Any more clarity and documentation covering sliding sessions would be greatly appreciated. Thanks!
@nsarno Hi. I don't understand how your project can be suitable for mobile devices without long lived refresh token...
@freemind- I think when you refresh, you're supposed to get a new refresh token that then has a new expiration
@freemind- what is the specificity around mobile devices?
I don't get it, how can I get new fresh token on request with valid authorization bearer token? How should response look like? I need just endpoint which will give me new fresh auth token. Nothing more ;)
Okay if anyone has problem like I had, there is solution. Just invoke:
token = Knock::AuthToken.new(payload: { sub: current_user.id }).token
with before_action :authenticate_user to set current_user.
Hope this helps, for me it was hard to find this method, so I think it can help someone.
@enterteg can you post a sample controller to exhibit how this works in context? I'm very interested in how you've implemented this. Thanks.
Sure, I don't know if I did it properly, if I did something wrong, please correct me ;)
class TokenActionsController < ApplicationController`
before_action :authenticate_user
def validate_token
# if token is working method returns 200 OK
# if not - authenticate_user returns 401 - not authenticated
render status: 200
end
def refresh_token
token = Knock::AuthToken.new(payload: { sub: current_user.id }).token
render json: { jwt: token }
end
end
For those that stumble on this thread in the future, here's another way to send back a token on every request (skip this action on controllers that don't need to be authenticated)
class ApplicationController < ActionController::API
include Knock::Authenticable
before_action :authenticate_and_set_token
private
def authenticate_and_set_token
authenticate_user
headers['Authorization'] = Knock::AuthToken.new(payload: current_user.to_token_payload).token
end
end
If you haven't set up a User.to_token_payload method, you can just use payload: { sub: current_user.id } inside Knock::AuthToken.new to return a default token.
I was able to get @aust1nz's auto refreshing code working, but with a few tweaks. I'm checking if the request includes the Authorization: Bearer Header, and if the token was valid, before refreshing the token. this way, routes that don't require authentication will still work as normal. I then continue to call before_action :authenticate_user for any specific routes that require authorization.
class ApplicationController < ActionController::API
include Knock::Authenticable
before_action :refresh_bearer_auth_header, if: :bearer_auth_header_present
private
def bearer_auth_header_present
request.env["HTTP_AUTHORIZATION"] =~ /Bearer/
end
def refresh_bearer_auth_header
authenticate_user
if current_user
headers['Authorization'] = Knock::AuthToken.new(payload: { sub: current_user.id }).token
end
end
end
What's not clear to me is how to handle the case where the user is inactive for more than 24 hours. Sliding sessions don't prevent that scenario. At that point I would like the front-end code to clear the token and log the user out but it's tricky to know if the front-end is getting 401 Unauthorized because of an expired token or because the user is actually trying to access a forbidden resource.
Put another way, I don't want to accidentally log out the user if he simply tries accessing a forbidden resource.
Is a common solution then to also keep track of the time the token was stored on the client side and clear it if more than 24 hours passes?
Edit: Another scenario that can happen is a server deploy happens and invalidates all existing tokens. How should that case be handled on the front-end?
@mhluska : can you tell us how you managed to do it?
Two notes
@catalinux at the time of writing I was using a React frontend and worked around this by storing a timestamp in localstorage. I've since switched to Ember and it seems ember-simple-auth-token handles that complexity for me.
Check my fork, I just implemented a secure refresh token for knock
https://github.com/ShiningBomb/knock
@ShiningBomb hey this looks really good - what are the limitations or security concerns with this? From the client perspective, where would you store refresh token? cookies?
Most helpful comment
For those that stumble on this thread in the future, here's another way to send back a token on every request (skip this action on controllers that don't need to be authenticated)
If you haven't set up a User.to_token_payload method, you can just use
payload: { sub: current_user.id }insideKnock::AuthToken.newto return a default token.