Abbreviation for JSON Web Token.
Please refer to the link below for details. https://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.html
It has three parts, a header, a payload, and a signature, each encoded in Base64.
header
{
"typ":"JWT",
"alg":"HS256"
}
payload
{
"sub": "1234567890",
"iss": "John Doe",
"aud": "audience",
"exp": 1353604926
}
signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
The three parts are connected by a. (Dot).
If you want to easily create a header, payload, and proof, you can do it with the link below. https://jwt.io/#debugger
JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM2ODA2In0.4fC4yLEmYTjiwaXk3R_AUUPEQSuI_ARmkoMqosWEJ-c
A JWT claim set is a JSON object, each member of which is a claim sent as a JWT. The claim name in the JWT claim set must be unique. This time, I will use the following four claims.
"iss" (Issuer) Claim: JWT issuer identifier "sub" (Subject) Claim: Identifier of the subject that is the subject of the JWT "aud" (Audience) Claim: List of identifiers of entities that are supposed to use JWT "exp" (Expiration Time): JWT expiration date
It is an implementation of signature verification for detecting tampering with request parameters. Let's try an implementation that adds a request parameter to the payload and verifies that the parameter and the parameter actually received have been tampered with.
Added params to payload
"params": "{\"email\":\"[email protected]\",\"password\":\"password\"}",
console
$ rails new jwt --api
gem 'jwt'
app/controllers/concerns/signature.rb
module Signature
extend ActiveSupport::Concern
def verify_signature
render status: 401, json: { message: 'Tampering was found.'} if request.headers['jwt-signature'].blank?
request_params = JSON.parse(request.body.read)
@signature ||= JwtSignature.new(jwt: request.headers['jwt-signature'])
@signature.verify!(params: request_params)
rescue JwtSignature::InvalidSignature
render status: 401, json: { message: 'Tampering was found.'}
end
end
app/models/jwt_signature.rb
class JwtSignature
class InvalidSignature < StandardError; end
ALGORITHM = 'HS256'
ISSUER = 'user'
AUDIENCE = 'audience'
SUB = "test"
TOKEN_TYPE = 'JWT'
# SECRET_KEY is important, so define it for each environment and manage it safely ~
SECRET_KEY = '1gCi6S9oaleH22KWaXyXZAQccBx4lUQi'
def initialize(jwt:)
@jwt = jwt
end
def verify!(params:)
raise InvalidSignature unless valid_payload? && valid_params?(params: params) && valid_header?
end
private
def valid_payload?
return false unless jwt_payload['iss'] == ISSUER
return false unless jwt_payload['sub'] == SUB
return false unless jwt_payload['aud'] == AUDIENCE
true
end
def valid_header?
return false unless jwt_header['alg'] == ALGORITHM
return false unless jwt_header['typ'] == TOKEN_TYPE
true
end
def valid_params?(params:)
JSON.parse(jwt_payload['params']) == params
end
def jwt_header
@jwt_header ||= decoded_jwt.second
end
def jwt_payload
@jwt_payload ||= decoded_jwt.first
end
def decoded_jwt
@decoded_jwt ||= JWT.decode(@jwt, SECRET_KEY, true, algorithm: ALGORITHM)
rescue JWT::DecodeError
raise InvalidSignature
end
end
Because JWT.decode returns a Hash Array Get Payload with decoded_jwt.first and Header with decoded_jwt.second
app / models / user.rb is generated
$ rails g model User name:string email:string token:string expired_at:datetime
$ rails db:migrate
routes.rb
Rails.application.routes.draw do
post 'tokens/create'
end
tokens_controller.rb
class TokensController < ActionController::API
include Signature
#Check signature verification only when creating
before_action :verify_signature, only: %i(create)
def create
#This time only the signature verification process is implemented
user = User.find_by(email: params[:email], password: params[:password])
return render status: 400, json: { message: 'User does not exist.' } unless user
#Update if Token does not exist
if user.token.blank?
user.token = SecureRandom.uuid
user.save
end
render status: 200, json: { name: user.name, email: user.email, token: user.token }
end
end
$rails c
irb(main):001:0> User.new(name: 'test', email: '[email protected]', password: 'password')
(0.5ms) SELECT sqlite_version(*)
=> #<User id: nil, name: "test", email: "[email protected]", password: [FILTERED], token: nil, expired_at: nil, created_at: nil, updated_at: nil>
irb(main):002:0> User.new(name: 'test', email: '[email protected]', password: 'password').save
(0.1ms) begin transaction
User Create (0.9ms) INSERT INTO "users" ("name", "email", "password", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "test"], ["email", "[email protected]"], ["password", "password"], ["created_at", "2020-07-04 02:30:17.701626"], ["updated_at", "2020-07-04 02:30:17.701626"]]
(0.8ms) commit transaction
=> true
https://jwt.io/#debugger Generate JwtEncoded data from the link above Payload part adds Jwt claims and parameters you need Add email and password to params and compare API parameter Json with Payload Json Change the Security key of VERIFY SIGNATURE to your own Security (using SECRET_KEY of JwtSignature model) If you want to set the maturity time, set Unix to exp
unixtime(5 minutes after the current time)
irb(main):040:0> (Time.now + 300).to_i
=> 1593838401
Add "jwt-signature" to Header and add JwtEncoded data
JwtEncoded data
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM4NDAxIn0.dSNqdhHBJKUJHnJa_2sS_3Qr4oNNdr5MKFx5ufwqLv4
Add email and password to Body in json format
json
{ "email": "[email protected]", "password": "password"}
After verifying the signature, I was able to get the token safely ~
Since 5 minutes was set for exp, signature verification failed after 5 minutes.
Recommended Posts