Skip to content

Commit

Permalink
Enable support for asymmetric algorithms
Browse files Browse the repository at this point in the history
Warden::JWTAuth added support for asymmetric algorithms, allowing the
specification of a separate decoding secret from the standard secret.

This, along with the change from #250, fixes the "No verification key
available" error as well as allows folks to use Devise::JWT with
asymmetric algorithms if they so choose.
  • Loading branch information
lauramosher authored and waiting-for-dev committed Sep 16, 2022
1 parent 104a4ab commit ff4615e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 5 deletions.
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ For `Denylist`, you only need to update the `include` line you're using in your

```ruby
# include Devise::JWT::RevocationStrategies::Blacklist # before
include Devise::JWT::RevocationStrategies::Denylist
include Devise::JWT::RevocationStrategies::Denylist
```

For `Allowlist`, you need to update the `include` line you're using in your user model:
Expand Down Expand Up @@ -78,9 +78,9 @@ end

If you are using Encrypted Credentials (Rails 5.2+), you can store the secret key in `config/credentials.yml.enc`.

Open your credentials editor using `bin/rails credentials:edit` and add `devise_jwt_secret_key`.
Open your credentials editor using `bin/rails credentials:edit` and add `devise_jwt_secret_key`.

**Note** you may need to set `$EDITOR` depending on your specific environment.
> **Note** you may need to set `$EDITOR` depending on your specific environment.
```yml

Expand All @@ -101,9 +101,32 @@ Devise.setup do |config|
end
```

**Important:** You are encouraged to use a secret different than your application `secret_key_base`. It is quite possible that some other component of your system is already using it. If several components share the same secret key, chances that a vulnerability in one of them has a wider impact increase. In rails, generating new secrets is as easy as `bundle exec rake secret`. Also, never share your secrets pushing it to a remote repository, you are better off using an environment variable like in the example.
> **Important:** You are encouraged to use a secret different than your application `secret_key_base`. It is quite possible that some other component of your system is already using it. If several components share the same secret key, chances that a vulnerability in one of them has a wider impact increase. In rails, generating new secrets is as easy as `bundle exec rake secret`. Also, never share your secrets pushing it to a remote repository, you are better off using an environment variable like in the example.
Currently, HS256 algorithm is the one in use.
Currently, HS256 algorithm is the one in use. You may configure a matching secret and algorithm name to use a different one (see [ruby-jwt](https://github.com/jwt/ruby-jwt#algorithms-and-usage) to see which are supported):

```ruby
Devise.setup do |config|
# ...
config.jwt do |jwt|
jwt.secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_secret_key!)
jwt.algorithm = Rails.application.credentials.devise_jwt_algorithm!
end
end
```

If the algorithm is asymmetric (e.g. RS256) which necessitates a different decoding secret, configure the `decoding_secret` setting as well:

```ruby
Devise.setup do |config|
# ...
config.jwt do |jwt|
jwt.secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_private_key!)
jwt.decoding_secret = OpenSSL::PKey::RSA.new(Rails.application.credentials.devise_jwt_public_key!)
jwt.algorithm = 'RS256' # or some other asymmetric algorithm
end
end
```

### Model configuration

Expand Down
6 changes: 6 additions & 0 deletions lib/devise/jwt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def self.forward_to_warden(setting, value)
default: Warden::JWTAuth.config.secret,
constructor: ->(value) { forward_to_warden(:secret, value) })

setting(:decoding_secret,
constructor: ->(value) { forward_to_warden(:decoding_secret, value) })

setting(:algorithm,
constructor: ->(value) { forward_to_warden(:algorithm, value) })

setting(:expiration_time,
default: Warden::JWTAuth.config.expiration_time,
constructor: ->(value) { forward_to_warden(:expiration_time, value) })
Expand Down
35 changes: 35 additions & 0 deletions spec/devise/jwt/railtie_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,39 @@
jwt_with_jti_matcher_user: JwtWithJtiMatcherUser
)
end

it 'configures algorithm using defaults' do
expect(Warden::JWTAuth.config.algorithm).to eq('HS256')
end

it 'configures decoding_secret using defaults' do
expect(Warden::JWTAuth.config.decoding_secret).to eq(
Warden::JWTAuth.config.secret
)
end

context 'when asymmetric algorithm is user configured' do
let(:rsa_secret) { OpenSSL::PKey::RSA.generate 2048 }
let(:decoding_secret) { rsa_secret.public_key }

before do
Rails.configuration.devise.jwt do |jwt|
jwt.algorithm = 'RS256'
jwt.secret = rsa_secret
jwt.decoding_secret = decoding_secret
end
end

it 'does not override user defined algorithm' do
expect(Warden::JWTAuth.config.algorithm).to eq('RS256')
end

it 'does not override user defined secret' do
expect(Warden::JWTAuth.config.secret).to eq(rsa_secret)
end

it 'does not override user defined decoding_secret' do
expect(Warden::JWTAuth.config.decoding_secret).to eq(decoding_secret)
end
end
end

0 comments on commit ff4615e

Please sign in to comment.