Skip to content

Commit 5239370

Browse files
feat: add connectAccountWithRedirect method to AuthService (#767)
1 parent d46dfc4 commit 5239370

4 files changed

Lines changed: 130 additions & 10 deletions

File tree

EXAMPLES.md

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Handling errors](#handling-errors)
1010
- [Organizations](#organizations)
1111
- [Standalone Components and a more functional approach](#standalone-components-and-a-more-functional-approach)
12+
- [Connect Accounts for using Token Vault](#connect-accounts-for-using-token-vault)
1213

1314
## Add login to your application
1415

@@ -157,7 +158,7 @@ import { AuthModule } from '@auth0/auth0-angular';
157158
clientId: 'YOUR_AUTH0_CLIENT_ID',
158159
authorizationParams: {
159160
audience: 'YOUR_AUTH0_API_IDENTIFIER',
160-
}
161+
},
161162
}),
162163
],
163164
// ...
@@ -278,7 +279,7 @@ AuthModule.forRoot({
278279
authorizationParams: {
279280
audience: 'http://my-api/',
280281
scope: 'write:orders',
281-
}
282+
},
282283
},
283284
},
284285
],
@@ -381,6 +382,7 @@ export class AppComponent {
381382
```
382383
383384
## Standalone components and a more functional approach
385+
384386
As of Angular 15, the Angular team is putting standalone components, as well as a more functional approach, in favor of the traditional use of NgModules and class-based approach.
385387
386388
There are a couple of difference with how you would traditionally implement our SDK:
@@ -398,18 +400,82 @@ const routes: Routes = [
398400
path: 'profile',
399401
component: ProfileComponent,
400402
canActivate: [authGuardFn],
401-
}
403+
},
402404
];
403405

404406
bootstrapApplication(AppComponent, {
405-
providers: [
406-
provideRouter(routes),
407-
provideAuth0(/* Auth Config Goes Here */),
408-
provideHttpClient(
409-
withInterceptors([authHttpInterceptorFn])
410-
)
411-
]
407+
providers: [provideRouter(routes), provideAuth0(/* Auth Config Goes Here */), provideHttpClient(withInterceptors([authHttpInterceptorFn]))],
412408
});
413409
```
414410
415411
Note that `provideAuth0` should **never** be provided to components, but only at the root level of your application.
412+
413+
## Connect Accounts for using Token Vault
414+
415+
The Connect Accounts feature uses the Auth0 My Account API to allow users to link multiple third party accounts to a single Auth0 user profile.
416+
417+
When using Connected Accounts, Auth0 acquires tokens from upstream Identity Providers (like Google) and stores them in a secure [Token Vault](https://auth0.com/docs/secure/tokens/token-vault). These tokens can then be used to access third-party APIs (like Google Calendar) on behalf of the user.
418+
419+
The tokens in the Token Vault are then accessible to [Resource Servers](https://auth0.com/docs/get-started/apis) (APIs) configured in Auth0. The SPA application can then issue requests to the API, which can retrieve the tokens from the Token Vault and use them to access the third-party APIs.
420+
421+
This is particularly useful for applications that require access to different resources on behalf of a user, like AI Agents.
422+
423+
### Configure the SDK
424+
425+
The SDK must be configured with an audience (an API Identifier) - this will be the resource server that uses the tokens from the Token Vault.
426+
427+
The SDK must also be configured to use refresh tokens and MRRT ([Multiple Resource Refresh Tokens](https://auth0.com/docs/secure/tokens/refresh-tokens/multi-resource-refresh-token)) since we will use the refresh token grant to get Access Tokens for the My Account API in addition to the API we are calling.
428+
429+
The My Account API requires DPoP tokens, so we also need to enable DPoP.
430+
431+
```ts
432+
AuthModule.forRoot({
433+
domain: '<AUTH0_DOMAIN>',
434+
clientId: '<AUTH0_CLIENT_ID>',
435+
useRefreshTokens: true,
436+
useMrrt: true,
437+
useDpop: true,
438+
authorizationParams: {
439+
redirect_uri: '<MY_CALLBACK_URL>',
440+
},
441+
});
442+
```
443+
444+
### Login to the application
445+
446+
Use the login methods to authenticate to the application and get a refresh and access token for the API.
447+
448+
```ts
449+
// Login specifying any scopes for the Auth0 API
450+
this.auth
451+
.loginWithRedirect({
452+
authorizationParams: {
453+
audience: '<AUTH0_API_IDENTIFIER>',
454+
scope: 'openid profile email read:calendar',
455+
},
456+
})
457+
.subscribe();
458+
```
459+
460+
### Connect to a third party account
461+
462+
Use the `connectAccountWithRedirect` method to redirect the user to the third party Identity Provider to connect their account.
463+
464+
```ts
465+
// Start the connect flow by redirecting to the third party API's login, defined as an Auth0 connection
466+
this.auth
467+
.connectAccountWithRedirect({
468+
connection: '<CONNECTION eg, google-apps-connection>',
469+
scopes: ['<SCOPE eg https://www.googleapis.com/auth/calendar.acls.readonly>'],
470+
authorizationParams: {
471+
// additional authorization params to forward to the authorization server
472+
},
473+
})
474+
.subscribe();
475+
```
476+
477+
You can now call the API with your access token and the API can use [Access Token Exchange with Token Vault](https://auth0.com/docs/secure/tokens/token-vault/access-token-exchange-with-token-vault) to get tokens from the Token Vault to access third party APIs on behalf of the user.
478+
479+
> **Important**
480+
>
481+
> You must enable Offline Access from the Connection Permissions settings to be able to use the connection with Connected Accounts.

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ describe('AuthService', () => {
6262
appState: undefined,
6363
} as any);
6464
jest.spyOn(auth0Client, 'loginWithRedirect').mockResolvedValue();
65+
jest.spyOn(auth0Client, 'connectAccountWithRedirect').mockResolvedValue();
6566
jest.spyOn(auth0Client, 'loginWithPopup').mockResolvedValue();
6667
jest.spyOn(auth0Client, 'checkSession').mockResolvedValue();
6768
jest.spyOn(auth0Client, 'isAuthenticated').mockResolvedValue(false);
@@ -669,6 +670,31 @@ describe('AuthService', () => {
669670
expect(auth0Client.loginWithRedirect).toHaveBeenCalledWith(options);
670671
});
671672

673+
it('should call `connectAccountWithRedirect`', async () => {
674+
const service = createService();
675+
const options = { connection: 'google-oauth2' };
676+
await service.connectAccountWithRedirect(options).toPromise();
677+
expect(auth0Client.connectAccountWithRedirect).toHaveBeenCalledWith(
678+
options
679+
);
680+
});
681+
682+
it('should call `connectAccountWithRedirect` and pass all options', async () => {
683+
const options = {
684+
connection: 'github',
685+
scopes: ['openid', 'profile', 'email'],
686+
authorization_params: { audience: 'https://api.github.com' },
687+
redirectUri: 'http://localhost:3000/callback',
688+
appState: { returnTo: '/profile' },
689+
};
690+
691+
const service = createService();
692+
await service.connectAccountWithRedirect(options).toPromise();
693+
expect(auth0Client.connectAccountWithRedirect).toHaveBeenCalledWith(
694+
options
695+
);
696+
});
697+
672698
it('should call `loginWithPopup`', (done) => {
673699
const service = createService();
674700
loaded(service).subscribe(() => {

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
RedirectLoginResult,
1010
GetTokenSilentlyVerboseResponse,
1111
ConnectAccountRedirectResult,
12+
RedirectConnectAccountOptions,
1213
CustomFetchMinimalOutput,
1314
Fetcher,
1415
FetcherConfig,
@@ -142,6 +143,32 @@ export class AuthService<TAppState extends AppState = AppState>
142143
return from(this.auth0Client.loginWithRedirect(options));
143144
}
144145

146+
/**
147+
* ```js
148+
* connectAccountWithRedirect({
149+
* connection: 'google-oauth2',
150+
* scopes: ['openid', 'profile', 'email', 'https://www.googleapis.com/auth/drive.readonly'],
151+
* authorization_params: {
152+
* // additional authorization params to forward to the authorization server
153+
* }
154+
* });
155+
* ```
156+
*
157+
* Redirects to the `/connect` URL using the parameters
158+
* provided as arguments. This then redirects to the connection's login page
159+
* where the user can authenticate and authorize the account to be connected.
160+
*
161+
* If connecting the account is successful, `handleRedirectCallback` will be called
162+
* with the details of the connected account.
163+
*
164+
* @param options The connect account options
165+
*/
166+
connectAccountWithRedirect(
167+
options: RedirectConnectAccountOptions<TAppState>
168+
): Observable<void> {
169+
return from(this.auth0Client.connectAccountWithRedirect(options));
170+
}
171+
145172
/**
146173
* ```js
147174
* await loginWithPopup(options);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
PopupConfigOptions,
2121
GetTokenWithPopupOptions,
2222
GetTokenSilentlyOptions,
23+
RedirectConnectAccountOptions,
2324
ICache,
2425
Cacheable,
2526
LocalStorageCache,

0 commit comments

Comments
 (0)