Request signing and verification for Internal APIs using JWT.
Add this line to your application's Gemfile:
gem 'jwt_signed_request'
then run:
$ bundle
We should be using a public key encryption algorithm such as ES256. To generate your public/private key pair using ES256 run:
$ openssl ecparam -genkey -name prime256v1 -noout -out myprivatekey.pem
$ openssl ec -in myprivatekey.pem -pubout -out mypubkey.pem
Store and encrypt these in your application secrets.
You can add signing and verification keys to the key store as your application needs them.
private_key = <<-pem.gsub(/^\s+/, "")
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBOQ3YIILYMV1glTKbF9oeZWzHe3SNQjAx4IbPIxNygQoAoGCCqGSM49
AwEHoUQDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/OexDdlmXEjHYaixzYIduluGXd
3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
-----END EC PRIVATE KEY-----
pem
public_key = <<-pem.gsub(/^\s+/, "")
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuOC3ufTTnW0hVmCPNERb4LxaDE/O
exDdlmXEjHYaixzYIduluGXd3cjg4H2gjqsY/NCpJ9nM8/AAINSrq+qPuA==
-----END PUBLIC KEY-----
pem
require 'openssl'
JWTSignedRequest.configure_keys do |config|
config.add_signing_key(
key_id: 'client_a',
key: OpenSSL::PKey::EC.new(private_key),
algorithm: 'ES256',
)
config.add_verification_key(
key_id: 'client_a',
key: OpenSSL::PKey::EC.new(public_key),
algorithm: 'ES256',
)
end
If you have added your signing keys to the key store, you will only need to specify the key_id
you are signing the requests with.
require 'net/http'
require 'uri'
require 'openssl'
require 'jwt_signed_request'
uri = URI('http://example.com')
req = Net::HTTP::Get.new(uri)
jwt_token = JWTSignedRequest.sign(
method: req.method,
path: req.path,
headers: {"Content-Type" => "application/json"},
body: "",
key_id: 'my-key-id', # used for looking up key and kid header
lookup_key_id: 'my-alt-key-id', # optionally override lookup key
issuer: 'my-issuer' # optional
additional_headers_to_sign: ['X-AUTH'] # optional
)
req['Authorization'] = "Bearer #{jwt_token}"
res = Net::HTTP.start(uri.hostname, uri.port) {|http|
http.request(req)
}
require 'faraday'
require 'openssl'
require 'jwt_signed_request/middlewares/faraday'
conn = Faraday.new(url: URI.parse('http://example.com')) do |faraday|
faraday.use JWTSignedRequest::Middlewares::Faraday,
key_id: 'my-key-id',
issuer: 'my-issuer', # optional
additional_headers_to_sign: ['X-AUTH'], # optional
bearer_schema: true # optional
faraday.adapter Faraday.default_adapter
end
conn.post do |req|
req.url 'http://example.com'
req.body = '{ "name": "Unagi" }'
end
Determines whether to use the Bearer schema when assigning the JWT token to the Authorization
request header
bearer_schema value | Authorization header value |
---|---|
false (default) | <jwt_token> |
true | Bearer <jwt_token> |
Please make sure you have added your verification keys to the key store. Doing so will allow the server to verify requests signed by different signing keys.
class APIController < ApplicationController
before_action :verify_request
...
private
def verify_request
begin
JWTSignedRequest.verify(request: request)
rescue JWTSignedRequest::UnauthorizedRequestError => e
render :json => {}, :status => :unauthorized
end
end
end
JWT tokens contain an expiry timestamp. If communication delays are large (or system clocks are sufficiently out of synch), you may need to increase the 'leeway' when verifying. For example:
JWTSignedRequest.verify(request: request, leeway: 55)
class Server < Sinatra::Base
use JWTSignedRequest::Middlewares::Rack,
exclude_paths: /public|health/, # optional regex
leeway: 100 # optional
end
Please note that the way we sign and verify requests has changed in version 2.x.x. For documentation on how to use older versions please look here.
We are only supporting the old API for the next couple of releases of version 2.x.x so please upgrade ASAP.
JWTSignedRequest
uses MIT license. See
LICENSE.txt
for
details.
We welcome contribution from everyone. Read more about it in
CODE_OF_CONDUCT.md
Many thanks to the following contributors to this gem:
- Toan Nguyen - @yoshdog
- Odin Dutton - @twe4ked
- Sebastian von Conrad - @vonconrad
- Zubin Henner- @zubin
- Glenn Tweedie - @nocache
- Giancarlo Salamanca - @salamagd
- Ben Axnick - @bentheax
- Glen Stampoultzis - @gstamp
- Lucas Parry - @lparry
- Chris Mckenzie - @chrisface
For bug fixes, documentation changes, and small features:
- Fork it ( https://github.com/envato/jwt_signed_request/fork )
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
For larger new features: Do everything as above, but first also make contact with the project maintainers to be sure your change fits with the project direction and you won't be wasting effort going in the wrong direction