Authorization Request by <%= @client.name %>
+ <%= form_tag authorizations_path do %> +-
+ <% requested_scopes.each do |scope| %>
+
- <%= scope %> + <% end %> +
<%= submit_tag :deny %>
+<%= submit_tag :approve %>
+ <% end %> +diff --git a/Gemfile b/Gemfile index 870551a..e4c9d03 100644 --- a/Gemfile +++ b/Gemfile @@ -13,3 +13,5 @@ gemspec # To use a debugger # gem 'byebug', group: [:development, :test] + +gem "openid_connect" \ No newline at end of file diff --git a/app/controllers/oidc_provider/authorizations_controller.rb b/app/controllers/oidc_provider/authorizations_controller.rb index 4bd1a88..8760b2b 100644 --- a/app/controllers/oidc_provider/authorizations_controller.rb +++ b/app/controllers/oidc_provider/authorizations_controller.rb @@ -4,6 +4,8 @@ module OIDCProvider class AuthorizationsController < ApplicationController include Concerns::ConnectEndpoint + skip_before_action :verify_authenticity_token + before_action :require_oauth_request before_action :require_response_type_code before_action :require_client @@ -15,13 +17,11 @@ def create authorization = build_authorization_with(requested_scopes) - oauth_response.code = authorization.code + oauth_response.code = authorization.code if @requested_type==:code or @requested_type == :hybrid + oauth_response.id_token = authorization.id_token.to_jwt if @requested_type==:id_token or @requested_type == :hybrid oauth_response.redirect_uri = @redirect_uri oauth_response.approve! - redirect_to oauth_response.location, allow_other_host: true - - # If we ever need to support denied authorizations that is done by: - # oauth_request.access_denied! + redirect_to oauth_response.location,allow_other_host: true end private @@ -31,7 +31,8 @@ def build_authorization_with(scopes) client_id: @client.identifier, nonce: oauth_request.nonce, scopes: scopes, - account: oidc_current_account + account: oidc_current_account, + issuer: request.base_url ) end @@ -46,9 +47,22 @@ def requested_scopes helper_method :requested_scopes def require_response_type_code - return if oauth_request.response_type == :code + type = oauth_request.response_type + type.nil? && oauth_request.unsupported_response_type! + case type + when :code + @requested_type=:code + when :id_token + @requested_type=:id_token + when ->(ary) do ary.include?(:code) && ary.include?(:id_token) end + @requested_type=:hybrid + # when type.include?("token") + # @response_type=:token + else + oauth_request.unsupported_response_type! + end + - oauth_request.unsupported_response_type! end def reset_login_if_necessary diff --git a/app/controllers/oidc_provider/discovery_controller.rb b/app/controllers/oidc_provider/discovery_controller.rb index 7ace84e..c0714e1 100644 --- a/app/controllers/oidc_provider/discovery_controller.rb +++ b/app/controllers/oidc_provider/discovery_controller.rb @@ -25,23 +25,24 @@ def webfinger_discovery end def openid_configuration + issuer = request.base_url config = OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: OIDCProvider.issuer, - authorization_endpoint: authorizations_url(host: OIDCProvider.issuer), - token_endpoint: tokens_url(host: OIDCProvider.issuer), - userinfo_endpoint: user_info_url(host: OIDCProvider.issuer), - end_session_endpoint: end_session_url(host: OIDCProvider.issuer), - jwks_uri: jwks_url(host: OIDCProvider.issuer), + issuer: issuer, + authorization_endpoint: authorizations_url(host: issuer), + token_endpoint: tokens_url(host: issuer), + userinfo_endpoint: user_info_url(host: issuer), + end_session_endpoint: end_session_url(host: issuer), + jwks_uri: jwks_url(host: issuer), scopes_supported: ["openid"] + OIDCProvider.supported_scopes.map(&:name), - response_types_supported: [:code], - grant_types_supported: [:authorization_code], + response_types_supported: [:code, :id_token], + grant_types_supported: [:authorization_code, :refresh_token], subject_types_supported: [:public], id_token_signing_alg_values_supported: [:RS256], token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post'], - claims_supported: ['sub', 'iss', 'name', 'email'] + claims_supported: ['sub', 'iss', 'name', 'email','aud','email_verified','family_name','given_name'] ) render json: config end end -end \ No newline at end of file +end diff --git a/app/controllers/oidc_provider/user_infos_controller.rb b/app/controllers/oidc_provider/user_infos_controller.rb index 30908bd..d4a8e0d 100644 --- a/app/controllers/oidc_provider/user_infos_controller.rb +++ b/app/controllers/oidc_provider/user_infos_controller.rb @@ -3,7 +3,8 @@ class UserInfosController < ApplicationController before_action :require_access_token def show - render json: AccountToUserInfo.new.(current_token.authorization.account, current_token.authorization.scopes) + + render json: AccountToUserInfo.new.(current_token.authorization.account, current_token.authorization.scopes, current_token.authorization.nonce) end end end \ No newline at end of file diff --git a/app/models/oidc_provider/access_token.rb b/app/models/oidc_provider/access_token.rb index 992b07f..9e959d9 100644 --- a/app/models/oidc_provider/access_token.rb +++ b/app/models/oidc_provider/access_token.rb @@ -7,11 +7,37 @@ class AccessToken < ApplicationRecord attribute :token, :string, default: -> { SecureRandom.hex 32 } attribute :expires_at, :datetime, default: -> { 1.hours.from_now } - def to_bearer_token - Rack::OAuth2::AccessToken::Bearer.new( - access_token: token, - expires_in: (expires_at - Time.now).to_i + def to_bearer_token(with_refresh_token) + if with_refresh_token + Rack::OAuth2::AccessToken::Bearer.new( + access_token: token, + expires_in: (expires_at - Time.now).to_i, + refresh_token: (get_refresh_token(authorization.client_id,authorization.scopes)||generate_refresh_token(authorization.client_id,authorization.scopes)).token, + scope: authorization.scopes + ) + else + Rack::OAuth2::AccessToken::Bearer.new( + access_token: token, + expires_in: (expires_at - Time.now).to_i, + scope: authorization.scopes + ) + end + end + + private + def get_refresh_token(client_id,scopes) + RefreshToken + .valid + .where(client_id: client_id, revoked_at: nil,scopes:scopes) + .first + end + def generate_refresh_token(client_id,scopes) + RefreshToken.create!( + client_id: client_id, + scopes: scopes, + authorization: authorization ) end + end end diff --git a/app/models/oidc_provider/authorization.rb b/app/models/oidc_provider/authorization.rb index 70bbcb8..44ffbd7 100644 --- a/app/models/oidc_provider/authorization.rb +++ b/app/models/oidc_provider/authorization.rb @@ -1,13 +1,14 @@ module OIDCProvider class Authorization < ApplicationRecord belongs_to :account, class_name: OIDCProvider.account_class - has_one :access_token + has_one :access_token, dependent: :destroy has_one :id_token scope :valid, -> { where(arel_table[:expires_at].gteq(Time.now.utc)) } attribute :code, :string, default: -> { SecureRandom.hex 32 } attribute :expires_at, :datetime, default: -> { 5.minutes.from_now } + attribute :issuer, :string serialize :scopes, coder: JSON @@ -20,6 +21,11 @@ def access_token super || expire! && generate_access_token! end + def refresh! + access_token = create_access_token! + access_token.save! + end + def id_token super || generate_id_token! end diff --git a/app/models/oidc_provider/id_token.rb b/app/models/oidc_provider/id_token.rb index 8a72d0f..1669b6f 100644 --- a/app/models/oidc_provider/id_token.rb +++ b/app/models/oidc_provider/id_token.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require 'openid_connect/response_object/id_token/id_token_with_user_info' module OIDCProvider class IdToken < ApplicationRecord @@ -11,14 +12,29 @@ class IdToken < ApplicationRecord delegate :account, to: :authorization def to_response_object - OpenIDConnect::ResponseObject::IdToken.new( - iss: OIDCProvider.issuer, + base_claims ={ + iss: authorization.issuer, sub: account.send(OIDCProvider.account_identifier), aud: authorization.client_id, nonce: nonce, exp: expires_at.to_i, - iat: created_at.to_i - ) + iat: created_at.to_i, + auth_time: authorization.created_at.to_i, + amr: [ "pwd" ] + } + if OIDCProvider.include_user_claims_in_id_token + extra_claims = { + email: account.send(OIDCProvider.account_email), + email_verified: true, + given_name: account.send(OIDCProvider.account_given_name), + family_name: account.send(OIDCProvider.account_family_name), + } + OpenIDConnect::ResponseObject::IdTokenWithUserInfo.new(**extra_claims, **base_claims) + else + OpenIDConnect::ResponseObject::IdToken.new( + **base_claims + ) + end end def to_jwt @@ -30,7 +46,6 @@ def to_jwt class << self def config { - issuer: OIDCProvider.issuer, jwk_set: JSON::JWK::Set.new(public_jwk) } end @@ -48,7 +63,7 @@ def private_jwk end def public_jwk - JSON::JWK.new key_pair.public_key + JSON::JWK.new key_pair.public_key, {use: 'sig',alg: 'RS256'} end end end diff --git a/app/models/oidc_provider/refresh_token.rb b/app/models/oidc_provider/refresh_token.rb new file mode 100644 index 0000000..f16e8a8 --- /dev/null +++ b/app/models/oidc_provider/refresh_token.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module OIDCProvider + class RefreshToken < ApplicationRecord + scope :valid, -> { where(arel_table[:expires_at].gteq(Time.now.utc)) } + belongs_to :authorization + attribute :token, :string, default: -> { SecureRandom.hex 32 } + attribute :expires_at, :datetime, default: -> { 1.month.from_now } + attribute :revoked_at, :datetime + serialize :scopes, coder: JSON + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..acfd9f6 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + +
+<%= submit_tag :deny %>
+<%= submit_tag :approve %>
+ <% end %> +