Skip to content

Commit 34ead65

Browse files
Merge pull request #1937 from timdeschryver/signals
feat: add authenticated and userData signals
2 parents 6e3a357 + b8bd181 commit 34ead65

File tree

9 files changed

+72
-110
lines changed

9 files changed

+72
-110
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -263,43 +263,3 @@ jobs:
263263
- name: Build Angular Application
264264
working-directory: ./angular-auth-oidc-client-test
265265
run: sudo npm run build
266-
267-
LibWithAngularV15:
268-
needs: build_job
269-
runs-on: ubuntu-latest
270-
name: Angular V15
271-
steps:
272-
- name: Setup Node.js
273-
uses: actions/setup-node@v2
274-
with:
275-
node-version: 16
276-
277-
- name: Download Artefact
278-
uses: actions/download-artifact@v2
279-
with:
280-
name: angular_auth_oidc_client_artefact
281-
path: angular-auth-oidc-client-artefact
282-
283-
- name: Install AngularCLI globally
284-
run: sudo npm install -g @angular/cli@15
285-
286-
- name: Show ng Version
287-
run: ng version
288-
289-
- name: Create Angular Project
290-
run: sudo ng new angular-auth-oidc-client-test --skip-git
291-
292-
- name: Npm Install & Install Library from local artefact
293-
run: |
294-
sudo cp -R angular-auth-oidc-client-artefact angular-auth-oidc-client-test/
295-
cd angular-auth-oidc-client-test
296-
sudo npm install --unsafe-perm=true
297-
sudo ng add ./angular-auth-oidc-client-artefact --authority-url-or-tenant-id "my-authority-url" --flow-type "OIDC Code Flow PKCE using refresh tokens" --use-local-package=true --skip-confirmation
298-
299-
- name: Test Angular Application
300-
working-directory: ./angular-auth-oidc-client-test
301-
run: npm test -- --watch=false --browsers=ChromeHeadless
302-
303-
- name: Build Angular Application
304-
working-directory: ./angular-auth-oidc-client-test
305-
run: sudo npm run build

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

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { TestBed, waitForAsync } from '@angular/core/testing';
22
import { Observable, of } from 'rxjs';
33
import { mockProvider } from '../test/auto-mock';
4-
import { AuthenticatedResult } from './auth-state/auth-result';
54
import { AuthStateService } from './auth-state/auth-state.service';
65
import { CheckAuthService } from './auth-state/check-auth.service';
76
import { CallbackService } from './callback/callback.service';
@@ -15,7 +14,6 @@ import { LoginService } from './login/login.service';
1514
import { LogoffRevocationService } from './logoff-revoke/logoff-revocation.service';
1615
import { OidcSecurityService } from './oidc.security.service';
1716
import { UserService } from './user-data/user.service';
18-
import { UserDataResult } from './user-data/userdata-result';
1917
import { TokenHelperService } from './utils/tokenHelper/token-helper.service';
2018
import { UrlService } from './utils/url/url.service';
2119

@@ -34,6 +32,8 @@ describe('OidcSecurityService', () => {
3432
let userService: UserService;
3533
let urlService: UrlService;
3634
let callbackService: CallbackService;
35+
let authenticatedSpy: jasmine.Spy;
36+
let userDataSpy: jasmine.Spy;
3737

3838
beforeEach(() => {
3939
TestBed.configureTestingModule({
@@ -58,10 +58,9 @@ describe('OidcSecurityService', () => {
5858
});
5959

6060
beforeEach(() => {
61-
oidcSecurityService = TestBed.inject(OidcSecurityService);
61+
authStateService = TestBed.inject(AuthStateService);
6262
tokenHelperService = TestBed.inject(TokenHelperService);
6363
configurationService = TestBed.inject(ConfigurationService);
64-
authStateService = TestBed.inject(AuthStateService);
6564
flowsDataService = TestBed.inject(FlowsDataService);
6665
logoffRevocationService = TestBed.inject(LogoffRevocationService);
6766
loginService = TestBed.inject(LoginService);
@@ -72,6 +71,11 @@ describe('OidcSecurityService', () => {
7271
authWellKnownService = TestBed.inject(AuthWellKnownService);
7372
checkSessionService = TestBed.inject(CheckSessionService);
7473
callbackService = TestBed.inject(CallbackService);
74+
75+
// this is required because these methods will be invoked by the signal properties when the service is created
76+
authenticatedSpy = spyOnProperty(authStateService, 'authenticated$').and.returnValue(of({ isAuthenticated: false, allConfigsAuthenticated: [] }));
77+
userDataSpy = spyOnProperty(userService, 'userData$').and.returnValue(of({ userData: null, allUserData: [] }));
78+
oidcSecurityService = TestBed.inject(OidcSecurityService);
7579
});
7680

7781
it('should create', () => {
@@ -80,29 +84,40 @@ describe('OidcSecurityService', () => {
8084

8185
describe('userData$', () => {
8286
it('calls userService.userData$', waitForAsync(() => {
83-
const spy = spyOnProperty(userService, 'userData$').and.returnValue(
84-
of({} as UserDataResult)
85-
);
86-
8787
oidcSecurityService.userData$.subscribe(() => {
88-
expect(spy).toHaveBeenCalledTimes(1);
88+
// 1x from this subscribe
89+
// 1x by the signal property
90+
expect(userDataSpy).toHaveBeenCalledTimes(2);
8991
});
9092
}));
9193
});
9294

95+
describe('userData', () => {
96+
it('calls userService.userData$', waitForAsync(() => {
97+
const _userdata = oidcSecurityService.userData();
98+
99+
expect(userDataSpy).toHaveBeenCalledTimes(1);
100+
}));
101+
});
102+
93103
describe('isAuthenticated$', () => {
94104
it('calls authStateService.isAuthenticated$', waitForAsync(() => {
95-
const spy = spyOnProperty(
96-
authStateService,
97-
'authenticated$'
98-
).and.returnValue(of({} as AuthenticatedResult));
99-
100105
oidcSecurityService.isAuthenticated$.subscribe(() => {
101-
expect(spy).toHaveBeenCalledTimes(1);
106+
// 1x from this subscribe
107+
// 1x by the signal property
108+
expect(authenticatedSpy).toHaveBeenCalledTimes(2);
102109
});
103110
}));
104111
});
105112

113+
describe('authenticated', () => {
114+
it('calls authStateService.isAuthenticated$', waitForAsync(() => {
115+
const _authenticated = oidcSecurityService.authenticated();
116+
117+
expect(authenticatedSpy).toHaveBeenCalledTimes(1);
118+
}));
119+
});
120+
106121
describe('checkSessionChanged$', () => {
107122
it('calls checkSessionService.checkSessionChanged$', waitForAsync(() => {
108123
const spy = spyOnProperty(

projects/angular-auth-oidc-client/src/lib/oidc.security.service.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { UserService } from './user-data/user.service';
2222
import { UserDataResult } from './user-data/userdata-result';
2323
import { TokenHelperService } from './utils/tokenHelper/token-helper.service';
2424
import { UrlService } from './utils/url/url.service';
25+
import { toSignal } from '@angular/core/rxjs-interop';
2526

2627
@Injectable({ providedIn: 'root' })
2728
export class OidcSecurityService {
@@ -61,6 +62,15 @@ export class OidcSecurityService {
6162
return this.userService.userData$;
6263
}
6364

65+
/**
66+
* Provides information about the user after they have logged in.
67+
*
68+
* @returns Returns an object containing either the user data directly (single config) or
69+
* the user data per config in case you are running with multiple configs
70+
*/
71+
userData = toSignal(this.userData$, {requireSync: true});
72+
73+
6474
/**
6575
* Emits each time an authorization event occurs.
6676
*
@@ -74,6 +84,17 @@ export class OidcSecurityService {
7484
return this.authStateService.authenticated$;
7585
}
7686

87+
/**
88+
* Emits each time an authorization event occurs.
89+
*
90+
* @returns Returns an object containing if you are authenticated or not.
91+
* Single Config: true if config is authenticated, false if not.
92+
* Multiple Configs: true is all configs are authenticated, false if only one of them is not
93+
*
94+
* The `allConfigsAuthenticated` property contains the auth information _per config_.
95+
*/
96+
authenticated = toSignal(this.isAuthenticated$, {requireSync: true});
97+
7798
/**
7899
* Emits each time the server sends a CheckSession event and the value changed. This property will always return
79100
* true.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<div>
2-
Normally you should not see this, authenticated == {{ isAuthenticated }}
2+
Normally you should not see this, authenticated == {{ authenticated().isAuthenticated }}
33
</div>
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
1-
import { Component, OnInit, inject } from '@angular/core';
1+
import { Component, inject } from '@angular/core';
22
import { OidcSecurityService } from 'angular-auth-oidc-client';
33

44
@Component({
55
selector: 'app-forbidden',
66
templateUrl: 'forbidden.component.html',
77
standalone: true,
88
})
9-
export class ForbiddenComponent implements OnInit {
9+
export class ForbiddenComponent {
1010
private readonly oidcSecurityService = inject(OidcSecurityService);
11-
12-
public isAuthenticated = false;
13-
14-
ngOnInit(): void {
15-
this.oidcSecurityService.isAuthenticated$.subscribe(
16-
({ isAuthenticated }) => {
17-
this.isAuthenticated = isAuthenticated;
18-
19-
console.warn('authenticated: ', isAuthenticated);
20-
}
21-
);
22-
}
11+
protected readonly authenticated = this.oidcSecurityService.authenticated;
2312
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div>Welcome to home Route</div>
22

33
<br />
4-
Is Authenticated: {{ isAuthenticated }}
5-
<pre>{{ userData$ | async | json }}</pre>
4+
Is Authenticated: {{ authenticated().isAuthenticated }}
5+
<pre>{{ userData() | json }}</pre>
Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
1-
import { AsyncPipe, JsonPipe } from '@angular/common';
2-
import { Component, OnInit, inject } from '@angular/core';
1+
import { JsonPipe } from '@angular/common';
2+
import { Component, inject } from '@angular/core';
33
import { OidcSecurityService } from 'angular-auth-oidc-client';
44

55
@Component({
66
selector: 'app-home',
77
templateUrl: 'home.component.html',
88
standalone: true,
9-
imports: [AsyncPipe, JsonPipe],
9+
imports: [JsonPipe],
1010
})
11-
export class HomeComponent implements OnInit {
11+
export class HomeComponent {
1212
private readonly oidcSecurityService = inject(OidcSecurityService);
13-
14-
userData$ = this.oidcSecurityService.userData$;
15-
16-
isAuthenticated = false;
17-
18-
ngOnInit(): void {
19-
this.oidcSecurityService.isAuthenticated$.subscribe(
20-
({ isAuthenticated }) => {
21-
this.isAuthenticated = isAuthenticated;
22-
23-
console.warn('authenticated: ', isAuthenticated);
24-
}
25-
);
26-
}
13+
protected readonly userData = this.oidcSecurityService.userData;
14+
protected readonly authenticated = this.oidcSecurityService.authenticated;
2715
}

projects/sample-code-flow-standalone/src/app/navigation/navigation.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,35 @@
1616
<div class="collapse navbar-collapse" id="topNavbarCollapse">
1717
<ul class="mr-auto navbar-nav">
1818
<li class="nav-item">
19-
<a class="nav-link" *ngIf="isAuthenticated" [routerLink]="['home']"
19+
<a class="nav-link" *ngIf="authenticated().isAuthenticated" [routerLink]="['home']"
2020
>home</a
2121
>
2222
</li>
2323
<li class="nav-item">
24-
<a class="nav-link" *ngIf="isAuthenticated" [routerLink]="['protected']"
24+
<a class="nav-link" *ngIf="authenticated().isAuthenticated" [routerLink]="['protected']"
2525
>protected</a
2626
>
2727
</li>
2828
<li class="nav-item">
29-
<a class="nav-link" *ngIf="isAuthenticated" [routerLink]="['forbidden']"
29+
<a class="nav-link" *ngIf="authenticated().isAuthenticated" [routerLink]="['forbidden']"
3030
>forbidden</a
3131
>
3232
</li>
3333
<li class="nav-item">
34-
<a class="nav-link" *ngIf="isAuthenticated" [routerLink]="['customers']"
34+
<a class="nav-link" *ngIf="authenticated().isAuthenticated" [routerLink]="['customers']"
3535
>lazy customers</a
3636
>
3737
</li>
3838
<li class="nav-item">
3939
<a
4040
class="nav-link"
41-
*ngIf="!isAuthenticated"
41+
*ngIf="!authenticated().isAuthenticated"
4242
[routerLink]="['protected']"
4343
>Route to 'protected' Route & Auto Login</a
4444
>
4545
</li>
4646
<li class="nav-item">
47-
<a class="nav-link" *ngIf="isAuthenticated" (click)="logout()"
47+
<a class="nav-link" *ngIf="authenticated().isAuthenticated" (click)="logout()"
4848
>Logout</a
4949
>
5050
</li>

projects/sample-code-flow-standalone/src/app/navigation/navigation.component.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NgIf } from '@angular/common';
2-
import { Component, OnInit, inject } from '@angular/core';
2+
import { Component, inject } from '@angular/core';
33
import { RouterLink } from '@angular/router';
44
import { OidcSecurityService } from 'angular-auth-oidc-client';
55

@@ -10,20 +10,9 @@ import { OidcSecurityService } from 'angular-auth-oidc-client';
1010
standalone: true,
1111
imports: [RouterLink, NgIf],
1212
})
13-
export class NavigationComponent implements OnInit {
13+
export class NavigationComponent {
1414
private readonly oidcSecurityService = inject(OidcSecurityService);
15-
16-
isAuthenticated = false;
17-
18-
ngOnInit(): void {
19-
this.oidcSecurityService.isAuthenticated$.subscribe(
20-
({ isAuthenticated }) => {
21-
this.isAuthenticated = isAuthenticated;
22-
23-
console.warn('authenticated: ', isAuthenticated);
24-
}
25-
);
26-
}
15+
protected readonly authenticated = this.oidcSecurityService.authenticated;
2716

2817
login(): void {
2918
this.oidcSecurityService.authorize();

0 commit comments

Comments
 (0)