Skip to content

Commit abd7a33

Browse files
feat: add DPoP support methods from @auth0/auth0-spa-js v2.10.0
- Add getDpopNonce() method to retrieve DPoP nonce for a domain - Add setDpopNonce() method to set DPoP nonce for a domain - Add generateDpopProof() method to generate DPoP proof JWT - Add createFetcher() method to create authenticated HTTP fetcher - Export DPoP-related types: Fetcher, FetcherConfig, CustomFetchMinimalOutput - Export UseDpopNonceError for error handling - Add comprehensive unit tests for all DPoP methods
1 parent 4cb7607 commit abd7a33

File tree

3 files changed

+196
-0
lines changed

3 files changed

+196
-0
lines changed

projects/auth0-angular/src/lib/auth.service.spec.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ describe('AuthService', () => {
7676
.spyOn(auth0Client, 'getTokenWithPopup')
7777
.mockResolvedValue('__access_token_from_popup__');
7878

79+
jest
80+
.spyOn(auth0Client, 'getDpopNonce')
81+
.mockResolvedValue('test-nonce-value');
82+
jest.spyOn(auth0Client, 'setDpopNonce').mockResolvedValue(undefined);
83+
jest
84+
.spyOn(auth0Client, 'generateDpopProof')
85+
.mockResolvedValue('test-proof-jwt');
86+
jest.spyOn(auth0Client, 'createFetcher').mockReturnValue({
87+
fetch: jest.fn(),
88+
} as any);
89+
7990
window.history.replaceState(null, '', '');
8091

8192
moduleSetup = {
@@ -982,4 +993,108 @@ describe('AuthService', () => {
982993
});
983994
});
984995
});
996+
997+
describe('getDpopNonce', () => {
998+
it('should retrieve DPoP nonce from the client', (done) => {
999+
const service = createService();
1000+
service.getDpopNonce().subscribe((nonce) => {
1001+
expect(nonce).toBe('test-nonce-value');
1002+
expect(auth0Client.getDpopNonce).toHaveBeenCalled();
1003+
done();
1004+
});
1005+
});
1006+
1007+
it('should pass domain identifier to the underlying SDK', (done) => {
1008+
const domainId = 'custom-domain';
1009+
const service = createService();
1010+
service.getDpopNonce(domainId).subscribe(() => {
1011+
expect(auth0Client.getDpopNonce).toHaveBeenCalledWith(domainId);
1012+
done();
1013+
});
1014+
});
1015+
1016+
it('should handle undefined nonce', (done) => {
1017+
(auth0Client.getDpopNonce as jest.Mock).mockResolvedValue(undefined);
1018+
const service = createService();
1019+
service.getDpopNonce().subscribe((nonce) => {
1020+
expect(nonce).toBeUndefined();
1021+
done();
1022+
});
1023+
});
1024+
});
1025+
1026+
describe('setDpopNonce', () => {
1027+
it('should set DPoP nonce through the client', (done) => {
1028+
const service = createService();
1029+
const nonceValue = 'new-nonce-123';
1030+
service.setDpopNonce(nonceValue).subscribe(() => {
1031+
expect(auth0Client.setDpopNonce).toHaveBeenCalledWith(
1032+
nonceValue,
1033+
undefined
1034+
);
1035+
done();
1036+
});
1037+
});
1038+
1039+
it('should pass nonce and domain identifier to the underlying SDK', (done) => {
1040+
const service = createService();
1041+
const nonceValue = 'nonce-456';
1042+
const domainId = 'domain-1';
1043+
service.setDpopNonce(nonceValue, domainId).subscribe(() => {
1044+
expect(auth0Client.setDpopNonce).toHaveBeenCalledWith(
1045+
nonceValue,
1046+
domainId
1047+
);
1048+
done();
1049+
});
1050+
});
1051+
});
1052+
1053+
describe('generateDpopProof', () => {
1054+
it('should generate DPoP proof JWT', (done) => {
1055+
const service = createService();
1056+
const params = {
1057+
url: 'https://api.example.com/resource',
1058+
method: 'POST',
1059+
accessToken: 'access-token-123',
1060+
};
1061+
service.generateDpopProof(params).subscribe((proof) => {
1062+
expect(proof).toBe('test-proof-jwt');
1063+
expect(auth0Client.generateDpopProof).toHaveBeenCalledWith(params);
1064+
done();
1065+
});
1066+
});
1067+
1068+
it('should pass all parameters including nonce', (done) => {
1069+
const service = createService();
1070+
const params = {
1071+
url: 'https://api.example.com/data',
1072+
method: 'GET',
1073+
nonce: 'server-nonce',
1074+
accessToken: 'token-xyz',
1075+
};
1076+
service.generateDpopProof(params).subscribe(() => {
1077+
expect(auth0Client.generateDpopProof).toHaveBeenCalledWith(params);
1078+
done();
1079+
});
1080+
});
1081+
});
1082+
1083+
describe('createFetcher', () => {
1084+
it('should create a fetcher instance', () => {
1085+
const service = createService();
1086+
const fetcher = service.createFetcher();
1087+
expect(fetcher).toBeDefined();
1088+
expect(auth0Client.createFetcher).toHaveBeenCalled();
1089+
});
1090+
1091+
it('should pass configuration to the underlying SDK', () => {
1092+
const service = createService();
1093+
const config = {
1094+
baseUrl: 'https://api.example.com',
1095+
};
1096+
service.createFetcher(config);
1097+
expect(auth0Client.createFetcher).toHaveBeenCalledWith(config);
1098+
});
1099+
});
9851100
});

projects/auth0-angular/src/lib/auth.service.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {
99
RedirectLoginResult,
1010
GetTokenSilentlyVerboseResponse,
1111
ConnectAccountRedirectResult,
12+
CustomFetchMinimalOutput,
13+
Fetcher,
14+
FetcherConfig,
1215
} from '@auth0/auth0-spa-js';
1316

1417
import {
@@ -329,6 +332,80 @@ export class AuthService<TAppState extends AppState = AppState>
329332
);
330333
}
331334

335+
/**
336+
* ```js
337+
* getDpopNonce(id).subscribe(nonce => ...)
338+
* ```
339+
*
340+
* Gets the DPoP nonce for the specified domain or the default domain.
341+
* The nonce is used in DPoP proof generation for token binding.
342+
*
343+
* @param id Optional identifier for the domain. If not provided, uses the default domain.
344+
* @returns An Observable that emits the DPoP nonce string or undefined if not available.
345+
*/
346+
getDpopNonce(id?: string): Observable<string | undefined> {
347+
return from(this.auth0Client.getDpopNonce(id));
348+
}
349+
350+
/**
351+
* ```js
352+
* setDpopNonce(nonce, id).subscribe(() => ...)
353+
* ```
354+
*
355+
* Sets the DPoP nonce for the specified domain or the default domain.
356+
* This is typically used after receiving a new nonce from the authorization server.
357+
*
358+
* @param nonce The DPoP nonce value to set.
359+
* @param id Optional identifier for the domain. If not provided, uses the default domain.
360+
* @returns An Observable that completes when the nonce is set.
361+
*/
362+
setDpopNonce(nonce: string, id?: string): Observable<void> {
363+
return from(this.auth0Client.setDpopNonce(nonce, id));
364+
}
365+
366+
/**
367+
* ```js
368+
* generateDpopProof(params).subscribe(proof => ...)
369+
* ```
370+
*
371+
* Generates a DPoP (Demonstrating Proof-of-Possession) proof JWT.
372+
* This proof is used to bind access tokens to a specific client, providing
373+
* an additional layer of security for token usage.
374+
*
375+
* @param params Configuration for generating the DPoP proof
376+
* @param params.url The URL of the resource server endpoint
377+
* @param params.method The HTTP method (e.g., 'GET', 'POST')
378+
* @param params.nonce Optional DPoP nonce from the authorization server
379+
* @param params.accessToken The access token to bind to the proof
380+
* @returns An Observable that emits the generated DPoP proof as a JWT string.
381+
*/
382+
generateDpopProof(params: {
383+
url: string;
384+
method: string;
385+
nonce?: string;
386+
accessToken: string;
387+
}): Observable<string> {
388+
return from(this.auth0Client.generateDpopProof(params));
389+
}
390+
391+
/**
392+
* ```js
393+
* const fetcher = createFetcher(config);
394+
* ```
395+
*
396+
* Creates a custom fetcher instance that can be used to make authenticated
397+
* HTTP requests. The fetcher automatically handles token refresh and can
398+
* be configured with custom request/response handling.
399+
*
400+
* @param config Optional configuration for the fetcher
401+
* @returns A Fetcher instance configured with the Auth0 client.
402+
*/
403+
createFetcher<TOutput extends CustomFetchMinimalOutput = Response>(
404+
config?: FetcherConfig<TOutput>
405+
): Fetcher<TOutput> {
406+
return this.auth0Client.createFetcher(config);
407+
}
408+
332409
private shouldHandleCallback(): Observable<boolean> {
333410
return of(location.search).pipe(
334411
map((search) => {

projects/auth0-angular/src/public-api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,8 @@ export {
3333
AuthenticationError,
3434
PopupCancelledError,
3535
MissingRefreshTokenError,
36+
Fetcher,
37+
FetcherConfig,
38+
CustomFetchMinimalOutput,
39+
UseDpopNonceError,
3640
} from '@auth0/auth0-spa-js';

0 commit comments

Comments
 (0)