Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JWT authentication #70

Closed
wants to merge 14 commits into from
Closed

Conversation

wilddima
Copy link

Hi!
I implemented JWT authentication as module and tested it works capacity in my pet project(SPA with React). Maybe, configurations is looking complex, but i tried to give more flexibility for customization authentication. I also tried to write some documentation, but my english is not good enough for it.

@wilddima wilddima changed the title Feature/jwt auth JWT authentication Jun 18, 2017
@Ch4s3
Copy link
Contributor

Ch4s3 commented Dec 13, 2017

@wilddima I'll try to take a look at this soon. THings got away from me over the summer, and I'm trying to catch up.

@Ch4s3 Ch4s3 requested a review from joshbuker December 13, 2017 04:38
@ebihara99999
Copy link
Contributor

ebihara99999 commented Dec 24, 2017

Thank you for great work! @wilddima
Would you show us a document or code snippets if the usage is wrong written bellow:

# app/sessions_controller.rb
def create
  render json: jwt_auth(params[:email], params[:password])
end

The response is like this:

{"user_data":{"id":1},"auth_token":"TOKEN"}

Then, access to a limited page with Authorization header with the value, the auth_token.

@athix and @Ch4s3: This PR seems good, having looked over this PR and RFC and jwt-ruby, and used it in a sandbox SPA rails app. The function has great advance in an API authentication if it's merged.

Copy link
Contributor

@ebihara99999 ebihara99999 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed some potential improvements.
The 'no new line at end of file' must be fixed, but I would like to discuss the error response design.

end
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to set the new line.

end
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to set the new line.

def jwt_not_authenticated
respond_to do |format|
format.html { not_authenticated }
format.json { render json: { status: 401 }, status: 401 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the message should not be the status code because it's verbose.
It would be better if the api response is like this (just an example)

format.json {
  render json: {
    "error": {
      "message": "Need to login first.",
    }
  }, 
  status: 401
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also suggest you use status: :unauthorized to make it more explicit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making this changeable, instead of hardcoding the return message

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's difficult to make an error message changeable because of the design. The cases are different when this method is called.

I use ruby-jwt error messages and I want it to be overridden if someone wants to change the message.

See this commit for details: ebihara99999@9e02135

@ebihara99999
Copy link
Contributor

@wilddima
Is there any configuration to set claims?
If so, we need to have discussion here which claims to be add.

@Spone
Copy link
Contributor

Spone commented Jan 13, 2018

I'm interested in this as well. Please let me know if I can help.

@ebihara99999
Copy link
Contributor

@Spone Thank you for saying that! I would appreciate it if you could review or give us feedbacks using this feature.

@joshbuker
Copy link
Member

I should be able to set aside time very soon to work on JWT, I'll take a look at this when I do. @Ch4s3 if you can set aside some time for this as well, I would like to get multiple eyes on something as big as this before releasing.

@ebihara99999
Copy link
Contributor

I prepared for a request and system spec helper to login. Should I open a PR to the master or this branch?


# This method creating JWT token by payload
def jwt_encode(payload)
JWT.encode(payload, Config.jwt_secret_key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should specify an algorithm to use, else the created JWT is unsigned!

See here: https://github.com/jwt/ruby-jwt#algorithms-and-usage

I think it should be a config option, with "HS256" as its default value.

The same algorithm parameter has to be passed to JWT.decode as well.

@Spone
Copy link
Contributor

Spone commented Mar 11, 2018

About the claims, I suggest adding by default:

  • exp (expiration date)
  • iat (issued at)

Also, as I understand it, the user ID could be added as the sub (subject) claim.

See https://tools.ietf.org/html/rfc7519#section-4.1 for the full list and https://github.com/jwt/ruby-jwt#add-custom-header-fields for details about the ruby-jwt implementation.

@ebihara99999
Copy link
Contributor

@Spone

Thanks for the review. I make it enable to configure claims in this commit: ebihara99999@4dde3c1

As for specifying an algorithm I will implement and open a PR if this PR keeps inactive.

acc[val] = user.public_send(val)
end
{ Config.jwt_user_data_key => user_params,
Config.jwt_auth_token_key => jwt_encode(user_params) }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's an idea to replace user_id with a jti (SecureRandom.uuid) and implement a whitelisting strategy to support login from multiple devices and independent logout. This implies that on login, a record is created in the whitelists table and that on logout this record gets deleted.

Also i'm missing expiration from this implementation. The generated jwt keys are valid indefinitely. The duration a key is persisted could be set from the config for instance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jovanmaric I don't know why user_id is here though the token includes user_id as the subject claim ( of course, it's needed to decode to acquire it ) .

I'd like to remove and only returns a token like eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjQyLCJleHAiOjE1NDc3MTkyMDAsImlhdCI6MTU0NzQ2MDAwMH0.QM5mTkYiDwI-10cEOq4b_bfrwe99BRuef6pnIB-jqIk.

What do you think about it?

And I would appreciate it if @wilddima could answer why Config.jwt_user_data_key => user_params is needed.

JWT.decode(token, Config.jwt_secret_key)[0]
)
rescue JWT::DecodeError
nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be removed, considering that def jwt_require_auth catches JWT::DecodeError, and omits an unauthorized_error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jovanmaric
I agree with that and changed like this:
9e02135

def jwt_not_authenticated
respond_to do |format|
format.html { not_authenticated }
format.json { render json: { status: 401 }, status: 401 }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making this changeable, instead of hardcoding the return message

@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
s.add_dependency 'oauth', '~> 0.4', '>= 0.4.4'
s.add_dependency 'oauth2', '~> 1.0', '>= 0.8.0'
s.add_dependency 'bcrypt', '~> 3.1'
s.add_dependency 'jwt', '~> 1.5'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest is 2.1.0

@jovanmaric
Copy link

Is there any eta on when this will be implemented? Considering that there is no defacto authentication gem which supports JWT.

@joshbuker joshbuker added the help wanted Community assistance requested label Sep 21, 2018
@joshbuker joshbuker added the high priority Extra attention is needed label Sep 21, 2018
@andrewhood125
Copy link
Contributor

I went about combing Sorcery and Knock to accomplish SPA and SSR authentication. I'd like to take a stab at this. It looks like Knock is not being very actively developed. I have a PR open against that gem to be able to support rails in API mode but it's been stale for months.

@joshbuker
Copy link
Member

@andrewhood125 any help would be greatly appreciated!

@Ch4s3 Ch4s3 mentioned this pull request Jan 11, 2017
10 tasks
@joshbuker
Copy link
Member

@andrewhood125 any luck?

@ebihara99999
Copy link
Contributor

@athix
I work on this PR modifying to reflect the PR comments after b06aa7a. I'll open a PR again if it's done.

ebihara99999 added a commit to ebihara99999/sorcery that referenced this pull request Jan 12, 2019
ebihara99999 added a commit to ebihara99999/sorcery that referenced this pull request Jan 12, 2019
There is a suggestion that a message in the case not authenticated could be changeable
in the discussion: Sorcery#70 (comment)

But I decide to make use of a message sent from `JWT::DecodeError` because
it's difficult to make the message flexible because of the design.

Changes:

- `#jwt_require_auth` only rescues `JWT::DecodeError` because JWT::VerificationError is a subclass.
- `#jwt_decode` raises `JWT::DecodeError` or the error of the subclasses, and
does not rescue in it.
- `#jwt_not_authenticated` receives an argument

`JWT::DecodeError` are from here: https://github.com/jwt/ruby-jwt/blob/master/lib/jwt/error.rb
@ebihara99999
Copy link
Contributor

@athix
I open a wip PR though not all are finished, because you can see the progress easier and I want review comments sooner. If it's OK to open as another PR, I would appreciate it if someone reviews.

ebihara99999 added a commit to ebihara99999/sorcery that referenced this pull request Jan 14, 2019
`JWT::ExpiredSignature: Signature has expired` is expected when a token is
expired.

Review comment is: Sorcery#70 (comment)
ebihara99999 added a commit to ebihara99999/sorcery that referenced this pull request Jan 15, 2019
@ebihara99999
Copy link
Contributor

@athix @Spone @Ch4s3 @jovanmaric
Almost all review comments are reflected and open a PR from b06aa7a ( I don't have access to commit to this PR ). I would appreciate it if you could review again!

#167

@privorotskii
Copy link

@athix Any updates?

@joshbuker
Copy link
Member

@TolichP Not at the moment unfortunately. If anyone from the community could help drive this effort, I would greatly appreciate the assistance. JWT would be a great addition, I just don't have time at the moment to drive it forward.

@joshbuker joshbuker added the to be implemented in v1 This issue or pull request will be resolved in the v1 rework, but has not yet been completed. label Apr 5, 2021
@joshbuker
Copy link
Member

Closing this out to reduce noise, see #239 and Sorcery/sorcery-rework#9 for the latest on JWT in Sorcery.

@joshbuker joshbuker closed this Apr 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Community assistance requested high priority Extra attention is needed to be implemented in v1 This issue or pull request will be resolved in the v1 rework, but has not yet been completed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants