1
1
require 'base64'
2
2
require 'uri'
3
3
require 'json'
4
+ require 'jwt'
4
5
5
6
module AuthressSdk
6
- class TokenValidationError < StandardError
7
- attr_reader :error_reason
8
- def initialize ( msg )
9
- @error_reason = msg
10
- super ( msg )
7
+ class TokenVerifier
8
+
9
+ attr_accessor :key_map
10
+
11
+ def initialize ( )
12
+ @key_map = { }
13
+ end
14
+
15
+ def verify_token ( authressCustomDomain , token )
16
+ sanitized_domain = authressCustomDomain . gsub ( /https?:\/ \/ / , '' )
17
+ completeIssuerUrl = "https://#{ sanitized_domain } "
18
+ if token . nil?
19
+ raise TokenVerificationError . new ( "Unauthorized: No token specified" )
20
+ end
21
+
22
+ begin
23
+ authenticationToken = token
24
+ unverifiedPayload = JWT . decode ( authenticationToken , nil , false )
25
+ rescue JWT ::DecodeError
26
+ begin
27
+ serviceClient = AuthressSdk ::ServiceClientTokenProvider . new ( token , completeIssuerUrl )
28
+ authenticationToken = serviceClient . get_token ( )
29
+ unverifiedPayload = JWT . decode ( authenticationToken , nil , false )
30
+ rescue Exception => e
31
+ raise TokenVerificationError . new ( "Unauthorized: Invalid Token format: #{ e } " )
32
+ end
33
+ end
34
+
35
+ if unverifiedPayload . nil?
36
+ raise TokenVerificationError . new ( "Unauthorized: Invalid Token or Token not found" )
37
+ end
38
+
39
+ kid = unverifiedPayload [ 1 ] [ "kid" ]
40
+ if kid . nil?
41
+ raise TokenVerificationError . new ( "Unauthorized: No KID found in token" )
42
+ end
43
+
44
+ issuer = unverifiedPayload [ 0 ] [ "iss" ]
45
+ if issuer . nil?
46
+ raise TokenVerificationError . new ( "Unauthorized: No Issuer in token" )
47
+ end
48
+
49
+ if ( URI ( issuer ) . host != URI ( completeIssuerUrl ) . host )
50
+ raise TokenVerificationError . new ( "Unauthorized: Issuer does not match" )
51
+ end
52
+
53
+ # Handle service client checking
54
+ issuerPath = URI ( issuer ) . path
55
+ clientIdMatcher = /^\/ v\d \/ clients\/ ([^\/ ]+)$/ . match ( issuerPath )
56
+ if clientIdMatcher && clientIdMatcher [ 1 ] != unverifiedPayload [ 0 ] [ 'sub' ]
57
+ raise TokenVerificationError . new ( "Unauthorized: Service ID does not match token sub claim" )
58
+ end
59
+
60
+ jwkObject = get_public_key ( "#{ issuer } /.well-known/openid-configuration/jwks" , kid )
61
+ jwk = jwkObject . verify_key ( )
62
+
63
+ begin
64
+ # https://github.com/jwt/ruby-jwt?tab=readme-ov-file
65
+ decodedResult = JWT . decode ( authenticationToken , jwk , true , { algorithm : 'EdDSA' } )
66
+ return decodedResult [ 0 ]
67
+ rescue Exception => e
68
+ raise TokenVerificationError . new ( "Unauthorized: Token is invalid - #{ e } " )
69
+ end
70
+ end
71
+
72
+ def get_public_key ( jwkKeyListUrl , kid )
73
+ hashKey = "#{ jwkKeyListUrl } |#{ kid } "
74
+
75
+ if @key_map [ hashKey ] . nil?
76
+ @key_map [ hashKey ] = get_key_uncached ( jwkKeyListUrl , kid )
77
+ end
78
+
79
+ begin
80
+ key = @key_map [ hashKey ]
81
+ return key
82
+ rescue
83
+ @key_map [ hashKey ] = get_key_uncached ( jwkKeyListUrl , kid )
84
+ return @key_map [ hashKey ]
85
+ end
11
86
end
87
+
88
+ def get_key_uncached ( jwkKeyListUrl , kid )
89
+ response = Typhoeus ::Request . new ( jwkKeyListUrl . to_s , { :method => :get , :ssl_verifypeer => true , :ssl_verifyhost => 2 , :verbose => false } ) . run
90
+ unless response . success?
91
+ raise TokenVerificationError . new ( "Unauthorized: Failed to fetch jwks from: #{ jwkKeyListUrl } " )
92
+ end
93
+
94
+ jwks = JWT ::JWK ::Set . new ( JSON . parse ( response . body ) )
95
+
96
+ key = jwks . find { |key | key [ :kid ] == kid }
97
+ if key
98
+ return key
99
+ end
100
+
101
+ raise TokenVerificationError . new ( "Unauthorized: KID was not found in the list of valid JWKs: #{ kid } " )
102
+ end
103
+
104
+ class TokenVerificationError < StandardError
105
+ attr_reader :error_reason
106
+ def initialize ( msg )
107
+ @error_reason = msg
108
+ super ( msg )
109
+ end
110
+ end
111
+
12
112
end
13
113
end
0 commit comments