This repository has been archived by the owner on Dec 8, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 28
/
class-b2c-token-checker.php
151 lines (121 loc) · 4.09 KB
/
class-b2c-token-checker.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php
use \Firebase\JWT\JWT;
/**
* A class to verify an id_token, following the implicit flow
* defined by the OpenID Connect standard.
*/
class B2C_Token_Checker {
private $id_token_array = array(); // still encoded
private $head = array(); // decoded
private $payload = array(); // decoded
private $clientID = '';
private $endpoint_handler;
function __construct($id_token, $clientID, $policy_name) {
$this->clientID = $clientID;
$this->endpoint_handler = new B2C_Endpoint_Handler($policy_name);
$this->split_id_token($id_token);
}
/**
* Converts base64url encoded string into base64 encoded string.
* Also adds the necessary padding to the base64 encoded string.
*/
private function convert_base64url_to_base64($data) {
return str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT);
}
/**
* Splits the id token into an array of header, payload, and signature.
*/
private function split_id_token($id_token) {
// Split the token into Header, Payload, and Signature, and decode
$this->id_token_array = explode('.', $id_token, 3);
$this->head = json_decode(base64_decode($this->id_token_array[0]), true);
$this->payload = json_decode(base64_decode($this->id_token_array[1]), true);
}
/**
* Validates the RSA signature on the token.
*/
private function validate_signature() {
//require_once "phpseclib/Crypt/RSA.php";
// Get kid from header
$kid = $this->head['kid'];
// For each JwksURI, get the public key and verify the signature
$key_datas = $this->endpoint_handler->get_jwks_uri_data();
foreach ($key_datas as $key_data) {
// Iterate through each key type until the one that matches the "kid" is found
$keys = json_decode($key_data, true)['keys'];
foreach ($keys as $key) {
if ($key['kid'] == $kid) {
$e = $key['e'];
$n = $key['n'];
break;
}
}
// 'e' and 'n' are base64 URL encoded, change to just base64 encoding
$e = $this->convert_base64url_to_base64($e);
$n = $this->convert_base64url_to_base64($n);
// Convert RSA(e,n) format to PEM format
$rsa = new Crypt_RSA();
$rsa->setPublicKey('<RSAKeyValue>
<Modulus>' . $n . '</Modulus>
<Exponent>' . $e . '</Exponent>
</RSAKeyValue>');
$public_key = $rsa->getPublicKey();
// Construct data and signature for verification
$to_verify_data = $this->id_token_array[0] . "." . $this->id_token_array[1];
$to_verify_sig = base64_decode($this->convert_base64url_to_base64(($this->id_token_array[2])));
try {
$jwt = $this->id_token_array[0] . "." . $this->id_token_array[1] . "." .$this->id_token_array[2];
$decoded = JWT::decode($jwt, $public_key, array('HS256', 'HS384', 'HS512', 'RS256'));
}
catch (Exception $e) {
echo 'error: ' . $e->getMessage();
return false;
}
}
// Returns true when verified successfully
return true;
}
/**
* Validates audience, not_before, expiration_time, and issuer claims.
*/
private function validate_claims() {
$audience = $this->payload['aud']; // Should be app's clientID
if ($audience != $this->clientID) {
return false;
}
$cur_time = time();
$not_before = $this->payload['nbf']; // epoch time, time after which token is valid (so basically nbf < cur time < exp)
$expiration = $this->payload['exp']; // epoch time, check that the token is still valid
if ($not_before > $cur_time) {
return false;
}
if ($cur_time > $expiration) {
return false;
}
// The Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim.
$iss_token = $this->payload['iss'];
$iss_metadata = $this->endpoint_handler->get_issuer();
if ($iss_token != $iss_metadata) {
return false;
}
return true;
}
/**
* Verifies both the signature and claims of the ID token.
*/
public function authenticate() {
if ($this->validate_signature() == false) {
return false;
}
if ($this->validate_claims() == false) {
return false;
}
return true;
}
/**
* Extracts a claim from the ID token.
*/
public function get_claim($name) {
return $this->payload[$name];
}
}