feat: implement authentication flow and dynamic API configuration #5
This commit is contained in:
3
Web/public/config.json
Normal file
3
Web/public/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"apiEndpoint": "https://localhost:5001"
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
||||
import { ApplicationConfig, inject, provideAppInitializer, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideOAuthClient } from 'angular-oauth2-oidc';
|
||||
import { AppConfigService } from './services/config.service';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZonelessChangeDetection(),
|
||||
provideRouter(routes),
|
||||
provideAppInitializer(() => inject(AppConfigService).loadConfig()),
|
||||
provideHttpClient(),
|
||||
provideOAuthClient({
|
||||
resourceServer: {
|
||||
|
||||
9
Web/src/app/app.route.guard.ts
Normal file
9
Web/src/app/app.route.guard.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CanActivateFn } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const authService = inject(OAuthService);
|
||||
return authService.hasValidAccessToken(); // returns boolean, Promise, or Observable
|
||||
};
|
||||
@@ -1,9 +1,20 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { Login } from './login/login';
|
||||
import { authGuard } from './app.route.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
component: Login
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: 'content',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () => import('./content/content').then(m => m.Content),
|
||||
canActivate: [authGuard],
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { APP_INITIALIZER, Component, inject, OnInit, provideAppInitializer } from '@angular/core';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { DefaultOAuthInterceptor, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { AppConfigService } from './services/config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet],
|
||||
providers: [OAuthService, {
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: DefaultOAuthInterceptor,
|
||||
multi: true,
|
||||
}],
|
||||
providers: [
|
||||
OAuthService,
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: DefaultOAuthInterceptor,
|
||||
multi: true,
|
||||
}],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss'
|
||||
})
|
||||
@@ -28,6 +31,6 @@ export class App implements OnInit {
|
||||
});
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.as.loadDiscoveryDocumentAndLogin();
|
||||
this.as.loadDiscoveryDocumentAndLogin().then(() => this.router.navigate(['login']));
|
||||
}
|
||||
}
|
||||
|
||||
1
Web/src/app/content/content.html
Normal file
1
Web/src/app/content/content.html
Normal file
@@ -0,0 +1 @@
|
||||
<span>{{data()|json}}</span>
|
||||
0
Web/src/app/content/content.scss
Normal file
0
Web/src/app/content/content.scss
Normal file
23
Web/src/app/content/content.spec.ts
Normal file
23
Web/src/app/content/content.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Content } from './content';
|
||||
|
||||
describe('Content', () => {
|
||||
let component: Content;
|
||||
let fixture: ComponentFixture<Content>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Content]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Content);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
23
Web/src/app/content/content.ts
Normal file
23
Web/src/app/content/content.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { JsonPipe } from '@angular/common';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Component, isDevMode, signal } from '@angular/core';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import { AppConfigService } from '../services/config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-content',
|
||||
imports: [JsonPipe],
|
||||
templateUrl: './content.html',
|
||||
styleUrl: './content.scss'
|
||||
})
|
||||
export class Content {
|
||||
data = signal({});
|
||||
|
||||
constructor(httpClient: HttpClient, readonly as: OAuthService, readonly cs: AppConfigService) {
|
||||
httpClient.get((isDevMode() ? cs.setting.apiEndpoint : '') + '/api/product', {
|
||||
headers: new HttpHeaders({ Authorization: `Bearer ${as.getAccessToken()}` }).append('Content-Type', 'application/json')
|
||||
}).subscribe(data => {
|
||||
this.data.set(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
<p>login works!</p>
|
||||
|
||||
Logging in...
|
||||
@@ -1,21 +1,33 @@
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [],
|
||||
templateUrl: './login.html',
|
||||
styleUrl: './login.scss'
|
||||
})
|
||||
export class Login implements OnInit {
|
||||
constructor(private readonly as: OAuthService, private readonly httpClient: HttpClient) {
|
||||
|
||||
}
|
||||
constructor(readonly as: OAuthService, readonly router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.httpClient.get('https://localhost:5001/api/product', {
|
||||
headers: new HttpHeaders({ Authorization: `Bearer ${this.as.getAccessToken()}` }).append('Content-Type', 'application/json')
|
||||
if (this.as.hasValidAccessToken() && this.as.hasValidIdToken()) {
|
||||
this.getUserInfo();
|
||||
} else {
|
||||
this.as.events.subscribe(event => {
|
||||
if (event.type === 'token_received')
|
||||
if (this.as.hasValidIdToken()) {
|
||||
this.getUserInfo();
|
||||
}
|
||||
})
|
||||
}
|
||||
).subscribe(console.warn);
|
||||
}
|
||||
|
||||
getUserInfo(): void {
|
||||
this.as.loadUserProfile().then(value => {
|
||||
console.log('User profile loaded:', value);
|
||||
this.router.navigate(['/content']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
19
Web/src/app/services/config.service.ts
Normal file
19
Web/src/app/services/config.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AppConfigService {
|
||||
private config: any;
|
||||
|
||||
constructor(private readonly http: HttpClient) { }
|
||||
|
||||
loadConfig(): Observable<any> {
|
||||
return this.http.get('/config.json')
|
||||
.pipe(tap(data => this.config = data))
|
||||
}
|
||||
|
||||
get setting() {
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user