Skip to content

Commit

Permalink
validate claims from server
Browse files Browse the repository at this point in the history
  • Loading branch information
LeipeLeon committed Sep 23, 2021
1 parent b3ee73f commit ed787d8
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 7 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,17 @@ AppleAuth::Token.new(code).authenticate!

### Handle server to server notifications

from the request parameter :payload

```ruby
# with a valid JWT
payload = "eyJraWQiOiJZ......"
AppleAuth::JWTDecoder.new(payload).call
params[:payload] = "eyJraWQiOiJZ......"
AppleAuth::ServerIdentity.new(params[:payload]).validate!
>> {iss: "https://appleid.apple.com", exp: 1632224024, iat: 1632137624, jti: "yctpp1ZHaGCzaNB9PWB4DA",...}

# with an invalid JWT
payload = "asdasdasdasd......"
AppleAuth::JWTDecoder.new(payload).call
params[:payload] = "asdasdasdasd......"
AppleAuth::ServerIdentity.new(params[:payload]).validate!
>> JWT::VerificationError: Signature verification raised
```

Expand All @@ -112,12 +114,12 @@ class Hooks::AuthController < ApplicationController
# NOTE: The Apple documentation states the events attribute as an array but is in fact a stringified json object
def apple
# will raise an error when the signature is invalid
payload = AppleAuth::JWTDecoder.new(params[:payload]).call
event = JSON.parse(payload["events"])
payload = AppleAuth::ServerIdentity.new(params[:payload]).validate!
event = JSON.parse(payload[:events]).symbolize_keys
uid = event["sub"]
user = User.find_by!(provider: 'apple', uid: uid)

case event["type"]
case event[:type]
when "email-enabled", "email-disabled"
# Here we should update the user with the relay state
when "consent-revoked", "account-delete"
Expand Down
2 changes: 2 additions & 0 deletions lib/apple_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
require 'apple_auth/helpers/conditions/iss_condition'
require 'apple_auth/helpers/jwt_conditions'
require 'apple_auth/helpers/jwt_decoder'
require 'apple_auth/helpers/jwt_server_conditions'

require 'apple_auth/server_identity'
require 'apple_auth/user_identity'
require 'apple_auth/token'

Expand Down
34 changes: 34 additions & 0 deletions lib/apple_auth/helpers/jwt_server_conditions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: false

module AppleAuth
class JWTServerConditions
include Conditions

CONDITIONS = [
AudCondition,
IatCondition,
IssCondition
].freeze

attr_reader :decoded_jwt

def initialize(decoded_jwt)
@decoded_jwt = decoded_jwt
end

def validate!
JWT::ClaimsValidator.new(decoded_jwt).validate! && jwt_conditions_validate!
rescue JWT::InvalidPayload => e
raise JWTValidationError, e.message
end

private

def jwt_conditions_validate!
conditions_results = CONDITIONS.map do |condition|
condition.new(decoded_jwt).validate!
end
conditions_results.all? { |value| value == true }
end
end
end
19 changes: 19 additions & 0 deletions lib/apple_auth/server_identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module AppleAuth
class ServerIdentity
attr_reader :jwt

def initialize(jwt)
@jwt = jwt
end

def validate!
token_data = JWTDecoder.new(jwt).call

JWTServerConditions.new(token_data).validate!

token_data.symbolize_keys
end
end
end
82 changes: 82 additions & 0 deletions spec/helpers/jwt_server_conditions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe AppleAuth::JWTServerConditions do
let(:jwt_sub) { '820417.faa325acbc78e1be1668ba852d492d8a.0219' }
let(:jwt_iss) { 'https://appleid.apple.com' }
let(:jwt_aud) { 'com.apple_auth' }
let(:jwt_iat) { Time.now.to_i }
let(:jwt) do
{
iss: jwt_iss,
aud: jwt_aud,
iat: jwt_iat,
events: '{
"type": "email-enabled",
"sub": "820417.faa325acbc78e1be1668ba852d492d8a.0219",
"email": "[email protected]",
"is_private_email": "true",
"event_time": 1508184845
}'
}
end

let(:decoded_jwt) { ActiveSupport::HashWithIndifferentAccess.new(jwt) }

before do
AppleAuth.config.apple_client_id = 'com.apple_auth'
end

subject(:jwt_conditions_helper) { described_class.new(decoded_jwt) }

context '#valid?' do
context 'when decoded jwt attributes are valid' do
it 'returns true' do
expect(jwt_conditions_helper.validate!).to eq(true)
end
end

context 'when jwt has incorrect type attributes' do
context 'when iat is not a integer' do
let(:jwt_iat) { Time.now }

it 'raises an exception' do
expect { jwt_conditions_helper.validate! }.to raise_error(
AppleAuth::Conditions::JWTValidationError
)
end
end
end

context 'when jwt_aud is different to apple_client_id' do
let(:jwt_aud) { 'net.apple_auth' }

it 'raises an exception' do
expect { jwt_conditions_helper.validate! }.to raise_error(
AppleAuth::Conditions::JWTValidationError, 'jwt_aud is different to apple_client_id'
)
end
end

context 'when jwt_iss is different to apple_iss' do
let(:jwt_iss) { 'https://appleid.apple.net' }

it 'raises an exception' do
expect { jwt_conditions_helper.validate! }.to raise_error(
AppleAuth::Conditions::JWTValidationError, 'jwt_iss is different to apple_iss'
)
end
end

context 'when jwt_iat is greater than now' do
let(:jwt_iat) { (Time.now + 5.minutes).to_i }

it 'raises an exception' do
expect { jwt_conditions_helper.validate! }.to raise_error(
AppleAuth::Conditions::JWTValidationError, 'jwt_iat is greater than now'
)
end
end
end
end
78 changes: 78 additions & 0 deletions spec/server_identity_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe AppleAuth::ServerIdentity do
let(:jwt_iss) { 'https://appleid.apple.com' }
let(:jwt_aud) { 'com.apple_sign_in' }
let(:jwt_iat) { Time.now.to_i }
let(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
let(:jwk) { JWT::JWK.new(private_key) }
let(:jwt) do
{
iss: jwt_iss,
aud: jwt_aud,
iat: jwt_iat,
events: '{
"type": "email-enabled",
"sub": "820417.faa325acbc78e1be1668ba852d492d8a.0219",
"email": "[email protected]",
"is_private_email": "true",
"event_time": 1508184845
}'
}
end

let(:signed_jwt) { JWT.encode(jwt, jwk.keypair, 'RS256', kid: jwk.kid) }
let(:exported_private_key) { JWT::JWK::RSA.new(private_key).export.merge({ alg: 'RS256' }) }
let(:apple_body) { [exported_private_key] }

before do
stub_request(:get, 'https://appleid.apple.com/auth/keys')
.to_return(
body: {
keys: apple_body
}.to_json,
status: 200,
headers: { 'Content-Type': 'application/json' }
)
AppleAuth.config.apple_client_id = jwt_aud
end

subject(:server_identity_service) { described_class.new(signed_jwt) }

context '#valid?' do
context 'when the parameters of the initilizer are correct' do
it 'returns the validated JWT attributes' do
expect(server_identity_service.validate!).to eq(jwt)
end

context 'when there are more than one private keys' do
let(:private_key_two) { OpenSSL::PKey::RSA.generate(2048) }
let(:exported_private_key_two) do
JWT::JWK::RSA.new(private_key).export.merge({ alg: 'RS256' })
end

it 'returns the validated JWT attributes' do
expect(server_identity_service.validate!).to eq(jwt)
end
end
end

context 'when the parameters of the initilizer are not correct' do
let(:jwt) do
{
iss: 'https://not-an-appleid.com',
aud: jwt_aud,
iat: jwt_iat
}
end

it 'raises an exception' do
expect { server_identity_service.validate! }.to raise_error(
AppleAuth::Conditions::JWTValidationError
)
end
end
end
end

0 comments on commit ed787d8

Please sign in to comment.