Skip to content

Commit a4e5d0a

Browse files
Merge pull request #223 from sangeetha5491/CI-7500
Fix sample codes and add id token claims
2 parents bba1170 + 2593893 commit a4e5d0a

File tree

2 files changed

+89
-82
lines changed

2 files changed

+89
-82
lines changed

src/pages/guides/authentication/AdminAuthentication/ims.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ https://id.adobe.com/consent
3232
| state | Yes | Adobe echoes back the value of the state parameter you supplied | Same as the value you supplied in the consent URL. |
3333
| id_token | No | Adobe provides an id token to enable the partner app to generate access tokens. This parameter is only present if the admin provided consent to your application. | A well formed JSON web token. |
3434

35+
36+
### Id Token Claims
37+
38+
| Claim Name | Description | Type |
39+
|------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------|
40+
| iss | The issuer of the id token. This will always be `https://ims-na1.adobelogin.com/ims` | String |
41+
| sub | The subject. More specifically, it's the technical account id of the consenting org. | String |
42+
| aud | The token audience, or the application that is supposed to use this token. This will always be the client ID of the partner app. | String |
43+
| exp | Unix seconds timestamp representing the expiry date of the token | Integer |
44+
| iat | Unix seconds timestamp representing the timestamp when the token was issued | Integer |
45+
| org_id | The organization ID of the customer who provided consent to the partner app. This is used to generate access tokens for this customer. | String |
46+
| nonce | The nonce value provided in the consent URL. This is used to protect against replay attacks. | String |
47+
3548
### Error codes
3649

3750

src/pages/guides/authentication/AdminAuthentication/samples.md

Lines changed: 76 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@ npm install jsonwebtoken axios jose
2929

3030
```js
3131
const axios = require('axios');
32-
const jwt = require('jsonwebtoken');
33-
const { importJWK } = require('jose');
32+
const { jwtVerify, importJWK } = require('jose');
3433

3534
/**
3635
* Verifies:
3736
* 1. session.state === state
3837
* 2. id token is valid
3938
* 3. session.nonce === payload.nonce
40-
*
39+
*
4140
* Returns org_id from token if valid
42-
*
41+
*
4342
* @param {string} idToken - The id_token from the redirect
4443
* @param {object} session - Contains expected `state` and `nonce`
4544
* @param {string} state - State from the redirect
@@ -52,25 +51,24 @@ async function verifyRedirect(idToken, session, state) {
5251
}
5352

5453
// Step 2: Decode header to get kid
55-
const decodedHeader = jwt.decode(idToken, { complete: true });
56-
if (!decodedHeader?.header?.kid) {
54+
const decodedHeader = JSON.parse(Buffer.from(idToken.split('.')[0], 'base64').toString());
55+
if (!decodedHeader?.kid) {
5756
throw new Error('Invalid id token: missing kid');
5857
}
5958

60-
const kid = decodedHeader.header.kid;
59+
const kid = decodedHeader.kid;
6160

6261
// Step 3: Fetch JWKS and get matching key
6362
const keys = await fetchAdobeKeys();
63+
6464
const jwk = keys.find(k => k.kid === kid);
6565
if (!jwk) {
6666
throw new Error(`No matching JWK found for kid: ${kid}`);
6767
}
6868

69-
// Step 4: Convert to public key
70-
const publicKey = await getPublicKeyFromJwk(jwk);
71-
72-
// Step 5: Verify id token signature
73-
const payload = jwt.verify(idToken, publicKey, {
69+
// Step 4: Import JWK and verify token
70+
const publicKey = await importJWK(jwk, jwk.alg);
71+
const { payload } = await jwtVerify(idToken, publicKey, {
7472
algorithms: ['RS256'],
7573
});
7674

@@ -80,11 +78,11 @@ async function verifyRedirect(idToken, session, state) {
8078
}
8179

8280
// Step 7: Return org_id
83-
if (!payload.org_id) {
81+
if (!payload.orgId) {
8482
throw new Error('org_id claim missing in id_token');
8583
}
8684

87-
return payload.org_id;
85+
return payload.orgId;
8886
}
8987

9088
/**
@@ -95,12 +93,7 @@ async function fetchAdobeKeys() {
9593
return response.data.keys;
9694
}
9795

98-
/**
99-
* Convert JWK to a usable public key
100-
*/
101-
async function getPublicKeyFromJwk(jwk) {
102-
return await importJWK(jwk, jwk.alg);
103-
}
96+
10497

10598
// Example usage
10699
(async () => {
@@ -137,71 +130,72 @@ from jwt import PyJWKClient
137130

138131
ADOBE_JWKS_URL = "https://ims-na1.adobelogin.com/ims/keys"
139132

140-
def verify_redirect(id_token: str, session: dict, state: str) -> str:
141-
"""
142-
Verifies:
143-
1. session.state == state
144-
2. id token is valid via Adobe public keys
145-
3. session.nonce == token's nonce
146-
4. Returns org_id from the token if all checks pass
147-
148-
:param id_token: The id_token returned from the redirect
149-
:param session: Dict with 'state' and 'nonce' keys
150-
:param state: The 'state' query parameter from redirect
151-
:return: org_id claim from the id_token
152-
:raises RedirectVerificationError: on any failure
153-
"""
154-
155-
# Step 1: Check state
156-
if session.get('state') != state:
157-
raise RedirectVerificationError("State mismatch")
158-
159-
# Step 2: Load signing key using PyJWKClient
160-
try:
161-
jwk_client = PyJWKClient(ADOBE_JWKS_URL)
162-
signing_key = jwk_client.get_signing_key_from_jwt(id_token)
163-
except Exception as e:
164-
raise RedirectVerificationError(f"JWK retrieval/lookup failed: {e}")
165-
166-
# Step 3: Verify id_token signature
167-
try:
168-
decoded = jwt.decode(
169-
id_token,
170-
signing_key.key,
171-
algorithms=["RS256"]
172-
)
173-
except jwt.PyJWTError as e:
174-
raise RedirectVerificationError(f"id token verification failed: {e}")
175-
176-
# Step 4: Nonce check
177-
if session.get('nonce') != decoded.get('nonce'):
178-
raise RedirectVerificationError("Nonce mismatch")
179-
180-
# Step 5: Return org_id
181-
org_id = decoded.get('org_id')
182-
if not org_id:
183-
raise RedirectVerificationError("org_id claim missing in token")
184-
185-
return org_id
133+
def verify_redirect(id_token: str, session: dict, state: str, client_id: str) -> str:
134+
"""
135+
Verifies:
136+
1. session.state == state
137+
2. id token is valid via Adobe public keys
138+
3. session.nonce == token's nonce
139+
4. Returns org_id from the token if all checks pass
140+
141+
:param id_token: The id_token returned from the redirect
142+
:param session: Dict with 'state' and 'nonce' keys
143+
:param state: The 'state' query parameter from redirect
144+
:return: org_id claim from the id_token
145+
:raises RedirectVerificationError: on any failure
146+
"""
147+
148+
# Step 1: Check state
149+
if session.get('state') != state:
150+
raise RedirectVerificationError("State mismatch")
151+
152+
# Step 2: Load signing key using PyJWKClient
153+
try:
154+
jwk_client = PyJWKClient(ADOBE_JWKS_URL)
155+
signing_key = jwk_client.get_signing_key_from_jwt(id_token)
156+
except Exception as e:
157+
raise RedirectVerificationError(f"JWK retrieval/lookup failed: {e}")
158+
159+
# Step 3: Verify id_token signature
160+
try:
161+
decoded = jwt.decode(
162+
id_token,
163+
signing_key.key,
164+
audience=client_id,
165+
algorithms=["RS256"]
166+
)
167+
except jwt.PyJWTError as e:
168+
raise RedirectVerificationError(f"id token verification failed: {e}")
169+
170+
# Step 4: Nonce check
171+
if session.get('nonce') != decoded.get('nonce'):
172+
raise RedirectVerificationError("Nonce mismatch")
173+
174+
# Step 5: Return org_id
175+
org_id = decoded.get('orgId')
176+
if not org_id:
177+
raise RedirectVerificationError("orgId claim missing in token")
178+
179+
return org_id
186180

187181
class RedirectVerificationError(Exception):
188-
pass
182+
pass
189183

190184
# Example usage
191185
if __name__ == "__main__":
192-
id_token = "your.id.token.here"
193-
session = {
194-
"state": "xyz123",
195-
"nonce": "abc456"
196-
}
197-
state = "xyz123"
198-
199-
try:
200-
org_id = verify_redirect(id_token, session, state)
201-
print("Verified org_id:", org_id)
202-
except RedirectVerificationError as e:
203-
print("Redirect verification failed:", e)
204-
186+
id_token = "your.id.token.here"
187+
session = {
188+
"state": "xyz123",
189+
"nonce": "abc456"
190+
}
191+
state = "xyz123"
192+
client_id = "your.application.client.id"
193+
194+
try:
195+
org_id = verify_redirect(id_token, session, state, client_id)
196+
print("Verified org_id:", org_id)
197+
except RedirectVerificationError as e:
198+
print("Redirect verification failed:", e)
205199
```
206200
207201
## Java
@@ -283,9 +277,9 @@ public class VerifyRedirect {
283277
}
284278

285279
// Step 6: Return org_id
286-
String orgId = verifiedJwt.getClaim("org_id").asString();
280+
String orgId = verifiedJwt.getClaim("orgId").asString();
287281
if (orgId == null || orgId.isEmpty()) {
288-
throw new Exception("org_id claim missing in token");
282+
throw new Exception("orgId claim missing in token");
289283
}
290284

291285
return orgId;

0 commit comments

Comments
 (0)