Commit c5e5d9a

mo <mo.khan@gmail.com>
2018-09-09 23:25:50
exchange refresh token for access token.
1 parent 03ab776
Changed files (4)
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/authorization.rb
@@ -18,8 +18,8 @@ class Authorization < ApplicationRecord
     transaction do
       revoke!
       [
-        tokens.create(subject: user, audience: client, token_type: :access),
-        tokens.create(subject: user, audience: client, token_type: :refresh),
+        tokens.create!(subject: user, audience: client, token_type: :access),
+        tokens.create!(subject: user, audience: client, token_type: :refresh),
       ]
     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