Commit c5e5d9a
Changed files (4)
app
controllers
models
spec
requests
app/controllers/oauths_controller.rb
@@ -23,6 +23,10 @@ class OauthsController < ApplicationController
if token_params[:grant_type] == 'authorization_code'
authorization = Authorization.active.find_by!(code: token_params[:code])
@access_token, @refresh_token = authorization.exchange
+ elsif token_params[:grant_type] == 'refresh_token'
+ refresh_token = token_params[:refresh_token]
+ claims = Token.claims_for(refresh_token, token_type: :refresh)
+ @access_token, @refresh_token = Token.find_by!(uuid: claims[:jti]).exchange
end
render formats: :json
rescue StandardError => error
@@ -33,6 +37,6 @@ class OauthsController < ApplicationController
private
def token_params
- params.permit(:grant_type, :code)
+ params.permit(:grant_type, :code, :refresh_token)
end
end
app/models/token.rb
@@ -2,7 +2,7 @@
class Token < ApplicationRecord
enum token_type: { access: 0, refresh: 1 }
- belongs_to :authorization
+ belongs_to :authorization, optional: true
belongs_to :subject, polymorphic: true
belongs_to :audience, polymorphic: true
@@ -20,6 +20,10 @@ class Token < ApplicationRecord
update!(revoked_at: Time.now)
end
+ def revoked?
+ revoked_at.present?
+ end
+
def claims(custom_claims = {})
{
aud: audience.to_param,
@@ -37,6 +41,16 @@ class Token < ApplicationRecord
@to_jwt ||= BearerToken.new.encode(claims(custom_claims))
end
+ def exchange
+ transaction do
+ revoke!
+ [
+ Token.create!(subject: subject, audience: audience, token_type: :access),
+ Token.create!(subject: subject, audience: audience, token_type: :refresh),
+ ]
+ end
+ end
+
class << self
def claims_for(token, token_type: :access)
if token_type == :any
spec/requests/oauth_spec.rb
@@ -98,5 +98,25 @@ RSpec.describe '/oauth' do
specify { expect(json[:error]).to eql('invalid_request') }
end
end
+
+ context "when exchanging a refresh token for a new access token" do
+ context "when the refresh token is still active" do
+ let(:refresh_token) { create(:refresh_token) }
+
+ before { post '/oauth/token', params: { grant_type: 'refresh_token', refresh_token: refresh_token.to_jwt } }
+
+ specify { expect(response).to have_http_status(:ok) }
+ specify { expect(response.headers['Content-Type']).to include('application/json') }
+ specify { expect(response.headers['Cache-Control']).to include('no-store') }
+ specify { expect(response.headers['Pragma']).to eql('no-cache') }
+
+ let(:json) { JSON.parse(response.body, symbolize_names: true) }
+ specify { expect(json[:access_token]).to be_present }
+ specify { expect(json[:token_type]).to eql('Bearer') }
+ specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
+ specify { expect(json[:refresh_token]).to be_present }
+ specify { expect(refresh_token.reload).to be_revoked }
+ end
+ end
end
end