Skip to content

Commit 14f032c

Browse files
Merge pull request #2006 from kyubisation/feat-app-initializer-auth-check
feat: add `withAppInitializerAuthCheck` as a feature for `provideAuth`
2 parents cb3080a + 49a71ee commit 14f032c

File tree

17 files changed

+206
-87
lines changed

17 files changed

+206
-87
lines changed

docs/site/angular-auth-oidc-client/docs/documentation/configuration.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,51 @@ export const appConfig: ApplicationConfig = {
219219
bootstrapApplication(AppComponent, appConfig);
220220
```
221221

222+
Additionally, you can use the feature function `withAppInitializerAuthCheck`
223+
to handle OAuth callbacks during app initialization phase. This replaces the
224+
need to manually call `OidcSecurityService.checkAuth(...)` or
225+
`OidcSecurityService.checkAuthMultiple(...)`.
226+
227+
```ts
228+
import { ApplicationConfig } from '@angular/core';
229+
import { provideAuth, withAppInitializerAuthCheck } from 'angular-auth-oidc-client';
230+
export const appConfig: ApplicationConfig = {
231+
providers: [
232+
provideAuth(
233+
{
234+
config: {
235+
/* Your config here */
236+
},
237+
},
238+
withAppInitializerAuthCheck()
239+
),
240+
],
241+
};
242+
```
243+
244+
If you prefer to manually check OAuth callback state, you can omit
245+
`withAppInitializerAuthCheck`. However, you then need to call
246+
`OidcSecurityService.checkAuth(...)` or
247+
`OidcSecurityService.checkAuthMultiple(...)` manually in your
248+
`app.component.ts` (or a similar code path that is called early in your app).
249+
250+
```ts
251+
// Shortened for brevity
252+
...
253+
export class AppComponent implements OnInit {
254+
private readonly oidcSecurityService = inject(OidcSecurityService);
255+
256+
ngOnInit(): void {
257+
this.oidcSecurityService
258+
.checkAuth()
259+
.subscribe(({ isAuthenticated, accessToken }) => {
260+
console.log('app authenticated', isAuthenticated);
261+
console.log(`Current access token is '${accessToken}'`);
262+
});
263+
}
264+
...
265+
```
266+
222267
## Config Values
223268
224269
### `configId`

projects/angular-auth-oidc-client/src/lib/provide-auth.spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { APP_INITIALIZER } from '@angular/core';
12
import { TestBed, waitForAsync } from '@angular/core/testing';
23
import { of } from 'rxjs';
34
import { mockProvider } from '../test/auto-mock';
@@ -8,7 +9,8 @@ import {
89
StsConfigLoader,
910
StsConfigStaticLoader,
1011
} from './config/loader/config-loader';
11-
import { provideAuth } from './provide-auth';
12+
import { OidcSecurityService } from './oidc.security.service';
13+
import { provideAuth, withAppInitializerAuthCheck } from './provide-auth';
1214

1315
describe('provideAuth', () => {
1416
describe('APP_CONFIG', () => {
@@ -55,4 +57,39 @@ describe('provideAuth', () => {
5557
expect(configLoader instanceof StsConfigHttpLoader).toBe(true);
5658
});
5759
});
60+
61+
describe('features', () => {
62+
let oidcSecurityServiceMock: jasmine.SpyObj<OidcSecurityService>;
63+
64+
beforeEach(waitForAsync(() => {
65+
oidcSecurityServiceMock = jasmine.createSpyObj<OidcSecurityService>(
66+
'OidcSecurityService',
67+
['checkAuthMultiple']
68+
);
69+
TestBed.configureTestingModule({
70+
providers: [
71+
provideAuth(
72+
{ config: { authority: 'something' } },
73+
withAppInitializerAuthCheck()
74+
),
75+
mockProvider(ConfigurationService),
76+
{
77+
provide: OidcSecurityService,
78+
useValue: oidcSecurityServiceMock,
79+
},
80+
],
81+
}).compileComponents();
82+
}));
83+
84+
it('should provide APP_INITIALIZER config', () => {
85+
const config = TestBed.inject(APP_INITIALIZER);
86+
87+
expect(config.length)
88+
.withContext('Expected an APP_INITIALIZER to be registered')
89+
.toBe(1);
90+
expect(oidcSecurityServiceMock.checkAuthMultiple).toHaveBeenCalledTimes(
91+
1
92+
);
93+
});
94+
});
5895
});

projects/angular-auth-oidc-client/src/lib/provide-auth.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
APP_INITIALIZER,
23
EnvironmentProviders,
34
makeEnvironmentProviders,
45
Provider,
@@ -11,13 +12,28 @@ import {
1112
import { StsConfigLoader } from './config/loader/config-loader';
1213
import { AbstractLoggerService } from './logging/abstract-logger.service';
1314
import { ConsoleLoggerService } from './logging/console-logger.service';
15+
import { OidcSecurityService } from './oidc.security.service';
1416
import { AbstractSecurityStorage } from './storage/abstract-security-storage';
1517
import { DefaultSessionStorageService } from './storage/default-sessionstorage.service';
1618

19+
/**
20+
* A feature to be used with `provideAuth`.
21+
*/
22+
export interface AuthFeature {
23+
ɵproviders: Provider[];
24+
}
25+
1726
export function provideAuth(
18-
passedConfig: PassedInitialConfig
27+
passedConfig: PassedInitialConfig,
28+
...features: AuthFeature[]
1929
): EnvironmentProviders {
20-
return makeEnvironmentProviders([..._provideAuth(passedConfig)]);
30+
const providers = _provideAuth(passedConfig);
31+
32+
for (const feature of features) {
33+
providers.push(...feature.ɵproviders);
34+
}
35+
36+
return makeEnvironmentProviders(providers);
2137
}
2238

2339
export function _provideAuth(passedConfig: PassedInitialConfig): Provider[] {
@@ -38,3 +54,26 @@ export function _provideAuth(passedConfig: PassedInitialConfig): Provider[] {
3854
{ provide: AbstractLoggerService, useClass: ConsoleLoggerService },
3955
];
4056
}
57+
58+
/**
59+
* Configures an app initializer, which is called before the app starts, and
60+
* resolves any OAuth callback variables.
61+
* When used, it replaces the need to manually call
62+
* `OidcSecurityService.checkAuth(...)` or
63+
* `OidcSecurityService.checkAuthMultiple(...)`.
64+
*
65+
* @see https://angular.dev/api/core/APP_INITIALIZER
66+
*/
67+
export function withAppInitializerAuthCheck(): AuthFeature {
68+
return {
69+
ɵproviders: [
70+
{
71+
provide: APP_INITIALIZER,
72+
useFactory: (oidcSecurityService: OidcSecurityService) => () =>
73+
oidcSecurityService.checkAuthMultiple(),
74+
multi: true,
75+
deps: [OidcSecurityService],
76+
},
77+
],
78+
};
79+
}

projects/sample-code-flow-azuread/src/app/app.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Component, inject } from '@angular/core';
2+
import { RouterOutlet } from '@angular/router';
23
import { OidcSecurityService } from 'angular-auth-oidc-client';
4+
import { NavMenuComponent } from './nav-menu/nav-menu.component';
35

46
@Component({
57
selector: 'app-root',
68
templateUrl: 'app.component.html',
9+
imports: [RouterOutlet, NavMenuComponent],
10+
standalone: true,
711
})
812
export class AppComponent {
913
private readonly oidcSecurityService = inject(OidcSecurityService);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {
2+
provideHttpClient,
3+
withInterceptors,
4+
withInterceptorsFromDi,
5+
} from '@angular/common/http';
6+
import { ApplicationConfig } from '@angular/core';
7+
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
8+
import { provideRouter } from '@angular/router';
9+
import {
10+
authInterceptor,
11+
LogLevel,
12+
provideAuth,
13+
withAppInitializerAuthCheck,
14+
} from 'angular-auth-oidc-client';
15+
16+
import { routes } from './app.routes';
17+
18+
export const appConfig: ApplicationConfig = {
19+
providers: [
20+
provideRouter(routes),
21+
provideAnimationsAsync(),
22+
provideAuth(
23+
{
24+
config: {
25+
authority:
26+
'https://login.microsoftonline.com/7ff95b15-dc21-4ba6-bc92-824856578fc1/v2.0',
27+
authWellknownEndpointUrl:
28+
'https://login.microsoftonline.com/common/v2.0',
29+
redirectUrl: window.location.origin,
30+
clientId: 'e38ea64a-2962-4cde-bfe7-dd2822fdab32',
31+
scope:
32+
'openid profile offline_access email api://e38ea64a-2962-4cde-bfe7-dd2822fdab32/access_as_user',
33+
responseType: 'code',
34+
silentRenew: true,
35+
maxIdTokenIatOffsetAllowedInSeconds: 600,
36+
issValidationOff: true,
37+
autoUserInfo: false,
38+
// silentRenewUrl: window.location.origin + '/silent-renew.html',
39+
useRefreshToken: true,
40+
logLevel: LogLevel.Debug,
41+
customParamsAuthRequest: {
42+
prompt: 'select_account', // login, consent
43+
},
44+
},
45+
},
46+
withAppInitializerAuthCheck()
47+
),
48+
provideHttpClient(
49+
withInterceptorsFromDi(),
50+
withInterceptors([authInterceptor()])
51+
),
52+
],
53+
};

projects/sample-code-flow-azuread/src/app/app.module.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1-
import { RouterModule, Routes } from '@angular/router';
2-
import { AutoLoginAllRoutesGuard } from 'angular-auth-oidc-client';
1+
import { Routes } from '@angular/router';
2+
import { autoLoginPartialRoutesGuard } from 'angular-auth-oidc-client';
33
import { ForbiddenComponent } from './forbidden/forbidden.component';
44
import { HomeComponent } from './home/home.component';
55
import { ProtectedComponent } from './protected/protected.component';
66
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
77

8-
const appRoutes: Routes = [
8+
export const routes: Routes = [
99
{ path: '', pathMatch: 'full', redirectTo: 'home' },
1010
{
1111
path: 'home',
1212
component: HomeComponent,
13-
canActivate: [AutoLoginAllRoutesGuard],
13+
canActivate: [autoLoginPartialRoutesGuard],
1414
},
1515
{
1616
path: 'forbidden',
1717
component: ForbiddenComponent,
18-
canActivate: [AutoLoginAllRoutesGuard],
18+
canActivate: [autoLoginPartialRoutesGuard],
1919
},
2020
{
2121
path: 'protected',
2222
component: ProtectedComponent,
23-
canActivate: [AutoLoginAllRoutesGuard],
23+
canActivate: [autoLoginPartialRoutesGuard],
2424
},
2525
{ path: 'unauthorized', component: UnauthorizedComponent },
2626
];
27-
28-
export const routing = RouterModule.forRoot(appRoutes);

projects/sample-code-flow-azuread/src/app/auth-config.module.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

projects/sample-code-flow-azuread/src/app/forbidden/forbidden.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('ForbiddenComponent', () => {
77

88
beforeEach(waitForAsync(() => {
99
TestBed.configureTestingModule({
10-
declarations: [ForbiddenComponent],
10+
imports: [ForbiddenComponent],
1111
}).compileComponents();
1212
}));
1313

projects/sample-code-flow-azuread/src/app/forbidden/forbidden.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import { Component } from '@angular/core';
44
selector: 'app-forbidden',
55
templateUrl: './forbidden.component.html',
66
styleUrls: ['./forbidden.component.css'],
7+
standalone: true,
78
})
89
export class ForbiddenComponent {}

0 commit comments

Comments
 (0)