ruby-on-railsdevisedevise-jwt

"No verification key available" when attempting to access API secured by Devise JWT


I have the gem devise-jwt installed. I can perform a login request, and receive an Authorization token in return, but when I try to access a secured endpoint, I receive the message: No verification key available.

blaine@devbox:~/langsite/backend [master] $ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwZWNjMmIzNi04ZmZiLTQ2Y2QtYTZkNi1iZGRjZmU4YTQxNmMiLCJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjA1ODQ2NjczLCJleHAiOjE2MDU4NzU0NzN9.ZyqvylXeLZbrRM2V2s5qsyHxiGgElng58HwQ8qjOHCU" http://localhost:3001/quiz_sentences.json
{"error":"No verification key available"}

This is what I have in my config/initializers/devise.rb file:

config.jwt do |jwt|
    jwt.secret = Rails.application.credentials.secret_key_jwt
    jwt.dispatch_requests = [
        ['POST', %r{^/users/sign_in$}],
        ['GET', %r{^/$}]
    ]
    jwt.request_formats = { user: [:json] }
    jwt.expiration_time = 8.hours.to_i
end

I can log in just fine and receive an Authorization token:

blaine@devbox:~/langsite/backend [master] $ curl -D - -X POST -d "user[email]=brlafreniere@gmail.com&user[password]=blaine" http://localhost:3001/users/sign_in.json
HTTP/1.1 201 Created
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Location: /
Content-Type: application/json; charset=utf-8
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwZWNjMmIzNi04ZmZiLTQ2Y2QtYTZkNi1iZGRjZmU4YTQxNmMiLCJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjA1ODQ4MDQ2LCJleHAiOjE2MDU4NzY4NDZ9.66Hg_NG3E79-ybC4rJK_XkkSxpLcWHWTlOiw96hyvjg
ETag: W/"cfe36cdecee4080492f63e8c8f0c091b"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: c961fc61-a1b4-49f0-bc16-63f19a0abd22
X-Runtime: 0.279213
Vary: Origin
Transfer-Encoding: chunked

It seems that I have the Authorization header exposed as well:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
    allow do
        origins '*'
        resource('*',
            headers: :any,
            expose: ["Authorization"],
            methods: :any
        )
    end
end

My user model:

class User < ApplicationRecord
    include Devise::JWT::RevocationStrategies::JTIMatcher
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
    devise :database_authenticatable, :registerable, :recoverable,
    :rememberable, :validatable, :jwt_authenticatable, jwt_revocation_strategy: self
end

I'm pretty much baffled, any help appreciated. Thanks.


Solution

  • TLDR; Confirm jwt.secret is actually being set

    I had this same issue, in my case it was caused because the jwt.secret was not being read correctly, when starting Puma via systemd.

    config.jwt do |jwt|
     jwt.secret = ENV['JWT_SECRET_KEY'] # was blank, only if starting via systemd or other daemon
     jwt.dispatch_requests = [
       ['POST', %r{^/login$}]
     ]
     jwt.revocation_requests = [
       ['DELETE', %r{^/logout$}]
     ]
     jwt.expiration_time = 2.weeks.to_i
    end
    

    For some reason, on the remote server, when launching via systemd service, env['JWT_SECRET_KEY'] was empty. However, when starting Puma manually, it worked fine.

    I found this out, by hard coding a string as the secret. Suddenly it worked.

    config.jwt do |jwt|
     jwt.secret = "012345678901234567890123456789" # Suddenly worked
     jwt.dispatch_requests = [
       ['POST', %r{^/login$}]
     ]
     jwt.revocation_requests = [
       ['DELETE', %r{^/logout$}]
     ]
     jwt.expiration_time = 2.weeks.to_i
    end 
    

    If jwt.secret is empty, it will still generate an auth token for whatever reason. Like you this threw me off, as it made me assume my setup was correct.


    To test if you're running into a similar issue, temporarily hardcode gibberish as the secret. If that works, then you're on the right track

    Obviously you should not hard code the string, but rather look into and confirm that your secret is being fed correctly into the above config.

    For me that meant specifying an EnvironmentFile in the systemd service configuration which contains key=value pairs (much like a dotenv file).