Skip to content

Commit 022bea7

Browse files
committed
Auth: Token: IAP: Google Claim
1 parent 1088e4f commit 022bea7

File tree

2 files changed

+152
-8
lines changed

2 files changed

+152
-8
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AffordableMobiles\GServerlessSupportLaravel\Auth\Token\Claims;
6+
7+
use ArrayAccess;
8+
use Illuminate\Support\Arr;
9+
10+
/**
11+
* Represents the structured 'google' claim from a validated IAP JWT.
12+
*
13+
* This provides type-safe, convenient access to device and access level
14+
* information required for Endpoint Verification checks. It validates the
15+
* incoming claim structure upon instantiation and implements ArrayAccess
16+
* for full backward compatibility without redundant data storage.
17+
*/
18+
class Google implements \ArrayAccess
19+
{
20+
/**
21+
* The unique identifier for the user's device.
22+
*
23+
* This is populated from the 'device_id' field in the JWT claim.
24+
*/
25+
public readonly ?string $deviceId;
26+
27+
/**
28+
* A list of access levels associated with the request.
29+
*
30+
* These correspond to the Endpoint Verification policies the
31+
* user's device has satisfied.
32+
*
33+
* @var string[]
34+
*/
35+
public readonly array $accessLevels;
36+
37+
/**
38+
* @param array $claim the 'google' claim array from the decoded JWT
39+
*/
40+
public function __construct(array $claim)
41+
{
42+
// Extract and assign the device_id and access_levels to readonly properties.
43+
// The Arr::get helper provides a safe way to access array keys, with a
44+
// default value if the key does not exist.
45+
$this->deviceId = Arr::get($claim, 'device_id');
46+
$this->accessLevels = Arr::get($claim, 'access_levels', []);
47+
}
48+
49+
/**
50+
* Get the device ID from the claim.
51+
*/
52+
public function getDeviceId(): ?string
53+
{
54+
return $this->deviceId;
55+
}
56+
57+
/**
58+
* Get the access levels from the claim.
59+
*/
60+
public function getAccessLevels(): array
61+
{
62+
return $this->accessLevels;
63+
}
64+
65+
/**
66+
* Check if the claim contains a specific, required access level.
67+
*
68+
* @param string $requiredLevel the full name of the access level to check for
69+
*/
70+
public function hasAccessLevel(string $requiredLevel): bool
71+
{
72+
return \in_array($requiredLevel, $this->accessLevels, true);
73+
}
74+
75+
/**
76+
* Determine if a claim key exists.
77+
*
78+
* @param mixed $offset
79+
*/
80+
public function offsetExists($offset): bool
81+
{
82+
return \in_array($offset, ['device_id', 'access_levels'], true);
83+
}
84+
85+
/**
86+
* Get a claim value by key, emulating array access.
87+
*
88+
* @param mixed $offset
89+
*/
90+
public function offsetGet($offset): mixed
91+
{
92+
return match ($offset) {
93+
'device_id' => $this->deviceId,
94+
'access_levels' => $this->accessLevels,
95+
default => null,
96+
};
97+
}
98+
99+
/**
100+
* Set a claim value (not supported).
101+
*
102+
* @param mixed $offset
103+
* @param mixed $value
104+
*
105+
* @throws \LogicException
106+
*/
107+
public function offsetSet($offset, $value): void
108+
{
109+
// This object is immutable; setting claims is not allowed.
110+
throw new \LogicException('Cannot modify a Google Claim object.');
111+
}
112+
113+
/**
114+
* Unset a claim value (not supported).
115+
*
116+
* @param mixed $offset
117+
*
118+
* @throws \LogicException
119+
*/
120+
public function offsetUnset($offset): void
121+
{
122+
// This object is immutable; unsetting claims is not allowed.
123+
throw new \LogicException('Cannot modify a Google Claim object.');
124+
}
125+
}

src/AffordableMobiles/GServerlessSupportLaravel/Auth/Token/IAP.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use AffordableMobiles\GServerlessSupportLaravel\Auth\Exception\InvalidTokenException;
88
use AffordableMobiles\GServerlessSupportLaravel\Auth\Token\Type\JWT;
9+
use Illuminate\Support\Arr;
910

1011
class IAP
1112
{
@@ -27,20 +28,38 @@ class IAP
2728
];
2829

2930
/**
30-
* Validate an IAP ID token.
31+
* Validate an IAP ID token and check for a required access level.
3132
*
32-
* @param string $iap_jwt the JWT token to be validated
33-
* @param string $expected_audience the expected audience of the provided JWT
33+
* This method cryptographically verifies the JWT. On success, it returns the
34+
* claims as an array, with the 'google' claim enriched into a `GoogleClaim` object.
3435
*
35-
* @return array returns array containing "sub" and "email" if token is valid
36+
* @param string $iap_jwt the JWT token to be validated
37+
* @param string $expected_audience the expected audience of the provided JWT
38+
* @param null|string $required_access_level the full name of the required Endpoint Verification access level
3639
*
37-
* @throws InvalidTokenException if the token is invalid
40+
* @return array the validated and enriched claims array
41+
*
42+
* @throws InvalidTokenException if the token is invalid or doesn't meet security requirements
3843
*/
39-
public static function validateToken($iap_jwt, $expected_audience)
44+
public static function validateToken(string $iap_jwt, string $expected_audience, ?string $required_access_level = null): array
4045
{
41-
$jwk_url = self::get_jwk_url();
46+
// Perform base cryptographic validation and check standard claims.
47+
$claims = JWT::validate($iap_jwt, $expected_audience, self::get_jwk_url(), self::JWT_SIG_ALG, self::JWT_ISSUERS);
48+
49+
// Enrich the 'google' claim into a structured object.
50+
$googleClaim = new Claims\Google(Arr::get($claims, 'google', []));
51+
$claims['google'] = $googleClaim;
52+
53+
// If an access level is required, perform the Endpoint Verification check.
54+
if ($required_access_level) {
55+
if (!$googleClaim->hasAccessLevel($required_access_level)) {
56+
throw new InvalidTokenException(
57+
'JWT is valid, but is missing the required access level for Endpoint Verification.'
58+
);
59+
}
60+
}
4261

43-
return JWT::validate($iap_jwt, $expected_audience, $jwk_url, self::JWT_SIG_ALG, self::JWT_ISSUERS);
62+
return $claims;
4463
}
4564

4665
/**

0 commit comments

Comments
 (0)