authGuard acc. lejdiprifti.com

This commit is contained in:
Andreas Knuth 2024-05-22 09:31:31 -05:00
parent 214327031c
commit 8fba3aa832
6 changed files with 198 additions and 109 deletions

View File

@ -1,7 +1,8 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { LogoutComponent } from './components/logout/logout.component'; import { LogoutComponent } from './components/logout/logout.component';
import { NotFoundComponent } from './components/not-found/not-found.component'; import { NotFoundComponent } from './components/not-found/not-found.component';
import { authGuard } from './guards/auth.guard';
import { KeycloakAuthGuard } from './guards/auth.guard';
import { ListingCategoryGuard } from './guards/listing-category.guard'; import { ListingCategoryGuard } from './guards/listing-category.guard';
import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component'; import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component';
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component'; import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
@ -64,62 +65,62 @@ export const routes: Routes = [
{ {
path: 'account', path: 'account',
component: AccountComponent, component: AccountComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
{ {
path: 'account/:id', path: 'account/:id',
component: AccountComponent, component: AccountComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// Create, Update Listings // Create, Update Listings
{ {
path: 'editBusinessListing/:id', path: 'editBusinessListing/:id',
component: EditBusinessListingComponent, component: EditBusinessListingComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
{ {
path: 'createBusinessListing', path: 'createBusinessListing',
component: EditBusinessListingComponent, component: EditBusinessListingComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
{ {
path: 'editCommercialPropertyListing/:id', path: 'editCommercialPropertyListing/:id',
component: EditCommercialPropertyListingComponent, component: EditCommercialPropertyListingComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
{ {
path: 'createCommercialPropertyListing', path: 'createCommercialPropertyListing',
component: EditCommercialPropertyListingComponent, component: EditCommercialPropertyListingComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// My Listings // My Listings
{ {
path: 'myListings', path: 'myListings',
component: MyListingComponent, component: MyListingComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// My Favorites // My Favorites
{ {
path: 'myFavorites', path: 'myFavorites',
component: FavoritesComponent, component: FavoritesComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// EMAil Us // EMAil Us
{ {
path: 'emailUs', path: 'emailUs',
component: EmailUsComponent, component: EmailUsComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// Logout // Logout
{ {
path: 'logout', path: 'logout',
component: LogoutComponent, component: LogoutComponent,
canActivate: [authGuard], canActivate: [KeycloakAuthGuard],
}, },
// ######### // #########
// Pricing // Pricing

View File

@ -1,42 +1,105 @@
import { inject } from '@angular/core'; // import { inject } from '@angular/core';
import { CanMatchFn, Router, UrlTree } from '@angular/router'; // import { CanMatchFn, Router, UrlTree } from '@angular/router';
// Services // // Services
import { KeycloakInitializerService } from '../services/keycloak-initializer.service'; // import { KeycloakInitializerService } from '../services/keycloak-initializer.service';
// import { KeycloakService } from '../services/keycloak.service';
import { KeycloakService } from '../services/keycloak.service'; import { KeycloakService } from '../services/keycloak.service';
import { createLogger } from '../utils/utils'; import { createLogger } from '../utils/utils';
const logger = createLogger('authGuard'); const logger = createLogger('authGuard');
export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => { // export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => {
const router = inject(Router); // const router = inject(Router);
const keycloakService = inject(KeycloakService); // const keycloakService = inject(KeycloakService);
const keycloakInitializer = inject(KeycloakInitializerService); // const keycloakInitializer = inject(KeycloakInitializerService);
if (!keycloakInitializer.isInitialized()) { // if (!keycloakInitializer.isInitialized()) {
await keycloakInitializer.initialize(); // await keycloakInitializer.initialize();
// }
// logger.info('###-> calling isLoggedIn()');
// const authenticated = keycloakService.isLoggedIn();
// if (!authenticated) {
// console.log(window.location.origin);
// console.log(window.location.href);
// keycloakService.login({
// redirectUri: `${window.location.origin}${segments['url']}`,
// });
// }
// // Get the user Keycloak roles and the required from the route
// const roles: string[] = keycloakService.getUserRoles(true);
// const requiredRoles = route.data?.['roles'];
// // Allow the user to proceed if no additional roles are required to access the route
// if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
// return true;
// }
// const authorized = requiredRoles.every(role => roles.includes(role));
// if (authorized) {
// return true;
// }
// return router.createUrlTree(['/home']);
// };
/**
* @license
* Copyright Mauricio Gemelli Vigolo and contributors.
*
* Use of this source code is governed by a MIT-style license that can be
* found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
*/
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
/**
* A simple guard implementation out of the box. This class should be inherited and
* implemented by the application. The only method that should be implemented is #isAccessAllowed.
* The reason for this is that the authorization flow is usually not unique, so in this way you will
* have more freedom to customize your authorization flow.
*/
export class KeycloakAuthGuard implements CanActivate {
/**
* Indicates if the user is authenticated or not.
*/
protected authenticated: boolean;
/**
* Roles of the logged user. It contains the clientId and realm user roles.
*/
protected roles: string[];
constructor(protected router: Router, protected keycloakAngular: KeycloakService) {}
/**
* CanActivate checks if the user is logged in and get the full list of roles (REALM + CLIENT)
* of the logged user. This values are set to authenticated and roles params.
*
* @param route
* @param state
*/
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree> {
try {
this.authenticated = await this.keycloakAngular.isLoggedIn();
this.roles = await this.keycloakAngular.getUserRoles(true);
return await this.isAccessAllowed(route, state);
} catch (error) {
throw new Error('An error happened during access validation. Details:' + error);
} }
logger.info('###-> calling isLoggedIn()');
const authenticated = keycloakService.isLoggedIn();
if (!authenticated) {
console.log(window.location.origin);
console.log(window.location.href);
keycloakService.login({
redirectUri: `${window.location.origin}${segments['url']}`,
});
} }
// Get the user Keycloak roles and the required from the route /**
const roles: string[] = keycloakService.getUserRoles(true); * Create your own customized authorization flow in this method. From here you already known
const requiredRoles = route.data?.['roles']; * if the user is authenticated (this.authenticated) and the user roles (this.roles).
*
// Allow the user to proceed if no additional roles are required to access the route * Return a UrlTree if the user should be redirected to another route.
if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) { *
return true; * @param route
* @param state
*/
async isAccessAllowed(route, state): Promise<boolean | UrlTree> {
if (!this.authenticated) {
await this.router.navigate(['/home']);
} }
return this.authenticated;
const authorized = requiredRoles.every(role => roles.includes(role));
if (authorized) {
return true;
} }
}
return router.createUrlTree(['/home']);
};

View File

@ -1,77 +1,95 @@
import { Injectable, inject } from '@angular/core'; /**
import { * @license
HttpInterceptor, * Copyright Mauricio Gemelli Vigolo and contributors.
HttpRequest, *
HttpHandler, * Use of this source code is governed by a MIT-style license that can be
HttpEvent, * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
HttpInterceptorFn, */
HttpHandlerFn,
} from '@angular/common/http'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, combineLatest, from, of } from 'rxjs'; import { Observable, combineLatest, from, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators'; import { mergeMap } from 'rxjs/operators';
import { KeycloakService } from '../services/keycloak.service';
import { ExcludedUrlRegex } from '../models/keycloak-options'; import { ExcludedUrlRegex } from '../models/keycloak-options';
import { KeycloakService } from '../services/keycloak.service';
export const keycloakBearerInterceptor: HttpInterceptorFn = (req, next) => { /**
//return next(req); * This interceptor includes the bearer by default in all HttpClient requests.
const keycloak = inject(KeycloakService); *
const { enableBearerInterceptor, excludedUrls } = keycloak; * If you need to exclude some URLs from adding the bearer, please, take a look
if (!enableBearerInterceptor) { * at the {@link KeycloakOptions} bearerExcludedUrls property.
return next(req); */
} @Injectable()
export class KeycloakBearerInterceptor implements HttpInterceptor {
constructor(private keycloak: KeycloakService) {}
const shallPass: boolean = /**
!keycloak.shouldAddToken(req) || * Calls to update the keycloak token if the request should update the token.
excludedUrls.findIndex((item) => isUrlExcluded(req, item)) > -1; *
if (shallPass) { * @param req http request from @angular http module.
return next(req); * @returns
} * A promise boolean for the token update or noop result.
*/
return combineLatest([ private async conditionallyUpdateToken(req: HttpRequest<unknown>): Promise<boolean> {
from(conditionallyUpdateToken(req)),
of(keycloak.isLoggedIn()),
]).pipe(
mergeMap(([_, isLoggedIn]) =>
isLoggedIn ? handleRequestWithTokenHeader(req, next) : next(req)
)
);
};
function isUrlExcluded(
{ method, url }: HttpRequest<unknown>,
{ urlPattern, httpMethods }: ExcludedUrlRegex
): boolean {
const httpTest =
httpMethods.length === 0 ||
httpMethods.join().indexOf(method.toUpperCase()) > -1;
const urlTest = urlPattern.test(url);
return httpTest && urlTest;
}
function handleRequestWithTokenHeader(
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> {
return this.keycloak.addTokenToHeader(req.headers).pipe(
mergeMap((headersWithBearer:string) => {
const kcReq = req.clone({
headers: req.headers.set('Authorization', headersWithBearer)
});//req.clone({ headers: headersWithBearer });
return next(kcReq);
})
);
}
async function conditionallyUpdateToken(
req: HttpRequest<unknown>
): Promise<boolean> {
if (this.keycloak.shouldUpdateToken(req)) { if (this.keycloak.shouldUpdateToken(req)) {
return await this.keycloak.updateToken(); return await this.keycloak.updateToken();
} }
return true; return true;
}
/**
* @deprecated
* Checks if the url is excluded from having the Bearer Authorization
* header added.
*
* @param req http request from @angular http module.
* @param excludedUrlRegex contains the url pattern and the http methods,
* excluded from adding the bearer at the Http Request.
*/
private isUrlExcluded({ method, url }: HttpRequest<unknown>, { urlPattern, httpMethods }: ExcludedUrlRegex): boolean {
const httpTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1;
const urlTest = urlPattern.test(url);
return httpTest && urlTest;
}
/**
* Intercept implementation that checks if the request url matches the excludedUrls.
* If not, adds the Authorization header to the request if the user is logged in.
*
* @param req
* @param next
*/
public intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const { enableBearerInterceptor, excludedUrls } = this.keycloak;
if (!enableBearerInterceptor) {
return next.handle(req);
}
const shallPass: boolean = !this.keycloak.shouldAddToken(req) || excludedUrls.findIndex(item => this.isUrlExcluded(req, item)) > -1;
if (shallPass) {
return next.handle(req);
}
return combineLatest([from(this.conditionallyUpdateToken(req)), of(this.keycloak.isLoggedIn())]).pipe(mergeMap(([_, isLoggedIn]) => (isLoggedIn ? this.handleRequestWithTokenHeader(req, next) : next.handle(req))));
}
/**
* Adds the token of the current user to the Authorization header
*
* @param req
* @param next
*/
private handleRequestWithTokenHeader(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return this.keycloak.addTokenToHeader(req.headers).pipe(
mergeMap(headersWithBearer => {
const kcReq = req.clone({ headers: headersWithBearer });
return next.handle(kcReq);
}),
);
}
} }

View File

@ -24,6 +24,11 @@ export class KeycloakInitializerService {
onLoad: 'check-sso', onLoad: 'check-sso',
silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html', silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html',
}, },
// initOptions: {
// pkceMethod: 'S256',
// redirectUri: environment.keycloak.redirectUri,
// checkLoginIframe: false,
// },
}); });
logger.info(`--->${authenticated}`); logger.info(`--->${authenticated}`);

View File

@ -7,5 +7,6 @@ export const environment_base = {
url: 'https://auth.bizmatch.net', url: 'https://auth.bizmatch.net',
realm: 'bizmatch-dev', realm: 'bizmatch-dev',
clientId: 'bizmatch-dev', clientId: 'bizmatch-dev',
redirectUri: 'https://dev.bizmatch.net',
}, },
}; };

View File

@ -5,3 +5,4 @@ environment.mailinfoUrl = 'http://localhost:4200';
environment.imageBaseUrl = 'http://localhost:4200'; environment.imageBaseUrl = 'http://localhost:4200';
environment.keycloak.clientId = 'dev'; environment.keycloak.clientId = 'dev';
environment.keycloak.realm = 'dev'; environment.keycloak.realm = 'dev';
environment.keycloak.redirectUri = 'http://localhost:4200';