Skip to content

Commit f38b49f

Browse files
feat: add DPoP support with fetcher API (#732)
1 parent 0595c48 commit f38b49f

File tree

6 files changed

+303
-10
lines changed

6 files changed

+303
-10
lines changed

package-lock.json

Lines changed: 95 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@angular/platform-browser": "^18.2.13",
3333
"@angular/platform-browser-dynamic": "^18.2.13",
3434
"@angular/router": "^18.2.13",
35-
"@auth0/auth0-spa-js": "^2.1.3",
35+
"@auth0/auth0-spa-js": "^2.10.0",
3636
"rxjs": "^6.6.7",
3737
"tslib": "^2.8.1",
3838
"zone.js": "~0.14.10"
@@ -57,6 +57,7 @@
5757
"browserstack-cypress-cli": "^1.32.8",
5858
"concurrently": "^6.2.0",
5959
"cors": "^2.8.5",
60+
"cross-fetch": "^4.1.0",
6061
"cypress": "^13.17.0",
6162
"eslint": "^8.57.0",
6263
"eslint-plugin-import": "latest",

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

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ describe('AuthService', () => {
5858
clientId: '',
5959
});
6060

61-
jest.spyOn(auth0Client, 'handleRedirectCallback').mockResolvedValue({});
61+
jest.spyOn(auth0Client, 'handleRedirectCallback').mockResolvedValue({
62+
appState: undefined,
63+
} as any);
6264
jest.spyOn(auth0Client, 'loginWithRedirect').mockResolvedValue();
6365
jest.spyOn(auth0Client, 'loginWithPopup').mockResolvedValue();
6466
jest.spyOn(auth0Client, 'checkSession').mockResolvedValue();
@@ -74,6 +76,17 @@ describe('AuthService', () => {
7476
.spyOn(auth0Client, 'getTokenWithPopup')
7577
.mockResolvedValue('__access_token_from_popup__');
7678

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+
7790
window.history.replaceState(null, '', '');
7891

7992
moduleSetup = {
@@ -980,4 +993,108 @@ describe('AuthService', () => {
980993
});
981994
});
982995
});
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+
});
9831100
});

0 commit comments

Comments
 (0)