From 4df6ab72fbceb20f4560e56cce63c1a69ef1c4cd Mon Sep 17 00:00:00 2001 From: Yogesh Chaudhary Date: Sun, 11 Jan 2026 16:52:53 +0530 Subject: [PATCH 1/4] feat: add connectAccountWithRedirect method to AuthService - Add connectAccountWithRedirect() method to AuthService - Export RedirectConnectAccountOptions type from public-api.ts - Exposes existing auth0-spa-js functionality to Angular SDK --- .../auth0-angular/src/lib/auth.service.ts | 27 +++++++++++++++++++ projects/auth0-angular/src/public-api.ts | 1 + 2 files changed, 28 insertions(+) diff --git a/projects/auth0-angular/src/lib/auth.service.ts b/projects/auth0-angular/src/lib/auth.service.ts index d5fb34b0..6e13a845 100644 --- a/projects/auth0-angular/src/lib/auth.service.ts +++ b/projects/auth0-angular/src/lib/auth.service.ts @@ -9,6 +9,7 @@ import { RedirectLoginResult, GetTokenSilentlyVerboseResponse, ConnectAccountRedirectResult, + RedirectConnectAccountOptions, CustomFetchMinimalOutput, Fetcher, FetcherConfig, @@ -142,6 +143,32 @@ export class AuthService return from(this.auth0Client.loginWithRedirect(options)); } + /** + * ```js + * connectAccountWithRedirect({ + * connection: 'google-oauth2', + * scopes: ['openid', 'profile', 'email', 'https://www.googleapis.com/auth/drive.readonly'], + * authorization_params: { + * // additional authorization params to forward to the authorization server + * } + * }); + * ``` + * + * Redirects to the `/connect` URL using the parameters + * provided as arguments. This then redirects to the connection's login page + * where the user can authenticate and authorize the account to be connected. + * + * If connecting the account is successful, `handleRedirectCallback` will be called + * with the details of the connected account. + * + * @param options The connect account options + */ + connectAccountWithRedirect( + options: RedirectConnectAccountOptions + ): Observable { + return from(this.auth0Client.connectAccountWithRedirect(options)); + } + /** * ```js * await loginWithPopup(options); diff --git a/projects/auth0-angular/src/public-api.ts b/projects/auth0-angular/src/public-api.ts index 2a17480f..f02da255 100644 --- a/projects/auth0-angular/src/public-api.ts +++ b/projects/auth0-angular/src/public-api.ts @@ -20,6 +20,7 @@ export { PopupConfigOptions, GetTokenWithPopupOptions, GetTokenSilentlyOptions, + RedirectConnectAccountOptions, ICache, Cacheable, LocalStorageCache, From dcdd1dff4a87d2c577fe0d2db1b78e932ad837e7 Mon Sep 17 00:00:00 2001 From: Yogesh Chaudhary Date: Mon, 12 Jan 2026 09:24:23 +0530 Subject: [PATCH 2/4] test: add unit tests for connectAccountWithRedirect method --- .../src/lib/auth.service.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/projects/auth0-angular/src/lib/auth.service.spec.ts b/projects/auth0-angular/src/lib/auth.service.spec.ts index af00d8a0..428685c9 100644 --- a/projects/auth0-angular/src/lib/auth.service.spec.ts +++ b/projects/auth0-angular/src/lib/auth.service.spec.ts @@ -62,6 +62,7 @@ describe('AuthService', () => { appState: undefined, } as any); jest.spyOn(auth0Client, 'loginWithRedirect').mockResolvedValue(); + jest.spyOn(auth0Client, 'connectAccountWithRedirect').mockResolvedValue(); jest.spyOn(auth0Client, 'loginWithPopup').mockResolvedValue(); jest.spyOn(auth0Client, 'checkSession').mockResolvedValue(); jest.spyOn(auth0Client, 'isAuthenticated').mockResolvedValue(false); @@ -669,6 +670,31 @@ describe('AuthService', () => { expect(auth0Client.loginWithRedirect).toHaveBeenCalledWith(options); }); + it('should call `connectAccountWithRedirect`', async () => { + const service = createService(); + const options = { connection: 'google-oauth2' }; + await service.connectAccountWithRedirect(options).toPromise(); + expect(auth0Client.connectAccountWithRedirect).toHaveBeenCalledWith( + options + ); + }); + + it('should call `connectAccountWithRedirect` and pass all options', async () => { + const options = { + connection: 'github', + scopes: ['openid', 'profile', 'email'], + authorization_params: { audience: 'https://api.github.com' }, + redirectUri: 'http://localhost:3000/callback', + appState: { returnTo: '/profile' }, + }; + + const service = createService(); + await service.connectAccountWithRedirect(options).toPromise(); + expect(auth0Client.connectAccountWithRedirect).toHaveBeenCalledWith( + options + ); + }); + it('should call `loginWithPopup`', (done) => { const service = createService(); loaded(service).subscribe(() => { From 4b71dc4f3c83b07eaec7bfc7c4ecfbe56058daa7 Mon Sep 17 00:00:00 2001 From: Yogesh Chaudhary Date: Mon, 12 Jan 2026 18:20:32 +0530 Subject: [PATCH 3/4] docs: add Connect Accounts example to EXAMPLES.md --- EXAMPLES.md | 72 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index a2ba7e56..db1c3b7e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -9,6 +9,7 @@ - [Handling errors](#handling-errors) - [Organizations](#organizations) - [Standalone Components and a more functional approach](#standalone-components-and-a-more-functional-approach) +- [Connect Accounts](#connect-accounts) ## Add login to your application @@ -157,7 +158,7 @@ import { AuthModule } from '@auth0/auth0-angular'; clientId: 'YOUR_AUTH0_CLIENT_ID', authorizationParams: { audience: 'YOUR_AUTH0_API_IDENTIFIER', - } + }, }), ], // ... @@ -278,7 +279,7 @@ AuthModule.forRoot({ authorizationParams: { audience: 'http://my-api/', scope: 'write:orders', - } + }, }, }, ], @@ -381,6 +382,7 @@ export class AppComponent { ``` ## Standalone components and a more functional approach + 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. There are a couple of difference with how you would traditionally implement our SDK: @@ -398,18 +400,68 @@ const routes: Routes = [ path: 'profile', component: ProfileComponent, canActivate: [authGuardFn], - } + }, ]; bootstrapApplication(AppComponent, { - providers: [ - provideRouter(routes), - provideAuth0(/* Auth Config Goes Here */), - provideHttpClient( - withInterceptors([authHttpInterceptorFn]) - ) - ] + providers: [provideRouter(routes), provideAuth0(/* Auth Config Goes Here */), provideHttpClient(withInterceptors([authHttpInterceptorFn]))], }); ``` Note that `provideAuth0` should **never** be provided to components, but only at the root level of your application. + +## Connect Accounts + +Link multiple identity providers to a single Auth0 user profile, allowing users to authenticate with any of their connected accounts. + +**Note:** User must be logged in first. + +### Configuration + +Enable `useRefreshTokens` and `useMrrt` in your Auth0 configuration: + +```ts +AuthModule.forRoot({ + domain: 'YOUR_AUTH0_DOMAIN', + clientId: 'YOUR_AUTH0_CLIENT_ID', + useRefreshTokens: true, + useMrrt: true, +}); +``` + +### Usage + +Use `connectAccountWithRedirect` to link an additional account: + +```ts +import { Component } from '@angular/core'; +import { AuthService } from '@auth0/auth0-angular'; + +@Component({ + selector: 'app-connect-account', + template: ``, +}) +export class ConnectAccountComponent { + constructor(private auth: AuthService) {} + + connectAccount(): void { + this.auth + .connectAccountWithRedirect({ + connection: 'google-oauth2', + scopes: ['openid', 'profile', 'email'], + appState: { returnTo: '/profile' }, + }) + .subscribe(); + } +} +``` + +After redirect, you can access connection details via the `appState$` observable: + +```ts +this.auth.appState$.subscribe((appState) => { + if (appState?.connectedAccount) { + console.log(`Connected to ${appState.connectedAccount.connection}`); + } +}); +``` From a64c0dc040e32182dac8157c07b29cf012792ebf Mon Sep 17 00:00:00 2001 From: Yogesh Chaudhary Date: Mon, 12 Jan 2026 18:54:26 +0530 Subject: [PATCH 4/4] docs: update Connect Accounts example with Token Vault context --- EXAMPLES.md | 86 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index db1c3b7e..efde66aa 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -9,7 +9,7 @@ - [Handling errors](#handling-errors) - [Organizations](#organizations) - [Standalone Components and a more functional approach](#standalone-components-and-a-more-functional-approach) -- [Connect Accounts](#connect-accounts) +- [Connect Accounts for using Token Vault](#connect-accounts-for-using-token-vault) ## Add login to your application @@ -410,58 +410,72 @@ bootstrapApplication(AppComponent, { Note that `provideAuth0` should **never** be provided to components, but only at the root level of your application. -## Connect Accounts +## Connect Accounts for using Token Vault -Link multiple identity providers to a single Auth0 user profile, allowing users to authenticate with any of their connected accounts. +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. -**Note:** User must be logged in first. +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. -### Configuration +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. -Enable `useRefreshTokens` and `useMrrt` in your Auth0 configuration: +This is particularly useful for applications that require access to different resources on behalf of a user, like AI Agents. + +### Configure the SDK + +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. + +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. + +The My Account API requires DPoP tokens, so we also need to enable DPoP. ```ts AuthModule.forRoot({ - domain: 'YOUR_AUTH0_DOMAIN', - clientId: 'YOUR_AUTH0_CLIENT_ID', + domain: '', + clientId: '', useRefreshTokens: true, useMrrt: true, + useDpop: true, + authorizationParams: { + redirect_uri: '', + }, }); ``` -### Usage +### Login to the application -Use `connectAccountWithRedirect` to link an additional account: +Use the login methods to authenticate to the application and get a refresh and access token for the API. ```ts -import { Component } from '@angular/core'; -import { AuthService } from '@auth0/auth0-angular'; - -@Component({ - selector: 'app-connect-account', - template: ``, -}) -export class ConnectAccountComponent { - constructor(private auth: AuthService) {} - - connectAccount(): void { - this.auth - .connectAccountWithRedirect({ - connection: 'google-oauth2', - scopes: ['openid', 'profile', 'email'], - appState: { returnTo: '/profile' }, - }) - .subscribe(); - } -} +// Login specifying any scopes for the Auth0 API +this.auth + .loginWithRedirect({ + authorizationParams: { + audience: '', + scope: 'openid profile email read:calendar', + }, + }) + .subscribe(); ``` -After redirect, you can access connection details via the `appState$` observable: +### Connect to a third party account + +Use the `connectAccountWithRedirect` method to redirect the user to the third party Identity Provider to connect their account. ```ts -this.auth.appState$.subscribe((appState) => { - if (appState?.connectedAccount) { - console.log(`Connected to ${appState.connectedAccount.connection}`); - } -}); +// Start the connect flow by redirecting to the third party API's login, defined as an Auth0 connection +this.auth + .connectAccountWithRedirect({ + connection: '', + scopes: [''], + authorizationParams: { + // additional authorization params to forward to the authorization server + }, + }) + .subscribe(); ``` + +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. + +> **Important** +> +> You must enable Offline Access from the Connection Permissions settings to be able to use the connection with Connected Accounts.