diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a8d21fb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +## [Unreleased] + +### Added + +- [#26](https://github.com/nhosoya/omniauth-apple/pull/26) Support ID Token verification + +### Changed + +- [#27](https://github.com/nhosoya/omniauth-apple/pull/27) Update development dependency + +## [0.0.3] - 2020-05-15 + +## [0.0.2] - 2020-01-16 + +## [0.0.1] - 2019-06-07 + +[Unreleased]: https://github.com/nhosoya/omniauth-apple/compare/v0.0.3...master diff --git a/lib/omniauth/strategies/apple.rb b/lib/omniauth/strategies/apple.rb index 332b5ef..681df63 100644 --- a/lib/omniauth/strategies/apple.rb +++ b/lib/omniauth/strategies/apple.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'omniauth-oauth2' +require 'net/https' module OmniAuth module Strategies @@ -14,7 +15,7 @@ class Apple < OmniAuth::Strategies::OAuth2 option :authorize_params, response_mode: 'form_post' option :authorized_client_ids, [] - + uid { id_info['sub'] } info do @@ -36,26 +37,62 @@ def client ::OAuth2::Client.new(client_id, client_secret, deep_symbolize(options.client_options)) end + def authorize_params + super.merge(nonce: new_nonce) + end + def callback_url options[:redirect_uri] || (full_host + script_name + callback_path) end private + def new_nonce + session['omniauth.nonce'] = SecureRandom.urlsafe_base64(16) + end + + def stored_nonce + session.delete('omniauth.nonce') + end + def id_info - if request.params&.key?('id_token') || access_token&.params&.key?('id_token') - id_token = request.params['id_token'] || access_token.params['id_token'] - log(:info, "id_token: #{id_token}") - @id_info ||= ::JWT.decode(id_token, nil, false)[0] # payload after decoding - end + @id_info ||= if request.params&.key?('id_token') || access_token&.params&.key?('id_token') + id_token = request.params['id_token'] || access_token.params['id_token'] + jwt_options = { + verify_iss: true, + iss: 'https://appleid.apple.com', + verify_iat: true, + verify_aud: true, + aud: [options.client_id].concat(options.authorized_client_ids), + algorithms: ['RS256'], + jwks: fetch_jwks + } + payload, _header = ::JWT.decode(id_token, nil, true, jwt_options) + verify_nonce!(payload) + payload + end end - def client_id - unless id_info.nil? - return id_info['aud'] if options.authorized_client_ids.include? id_info['aud'] - end + def fetch_jwks + uri = URI.parse('https://appleid.apple.com/auth/keys') + response = Net::HTTP.get_response(uri) + { keys: JSON.parse(response.body)['keys'].map(&:with_indifferent_access) } + end + + def verify_nonce!(payload) + return unless payload[:nonce_supported] + + return if payload[:nonce].present? && payload[:nonce] != stored_nonce - options.client_id + fail!(:nonce_mismatch, CallbackError.new(:nonce_mismatch, 'nonce mismatch')) + end + + def client_id + @client_id ||= if id_info.nil? + options.client_id + else + id_info['aud'] if options.authorized_client_ids.include? id_info['aud'] + end end def user_info