How to Secure Angular4 app with Keycloak
https://www.czetsuyatech.com/2017/10/angular-security-with-keycloak.html
Disclaimer: This is not a tutorial on how to set up an Angular^4 project, nor are we going to discuss how to set up a Keycloak server. Needless to say, you should already be familiar with these tech before diving here.
I have a previous article you may want to check that discusses keycloak http://czetsuya-tech.blogspot.com/2017/04/how-to-signin-to-keycloak-using-google.html.
After creating your client in Keycloak, download the Keycloak OIDC JSON, under the Installation tab as keycloak.json. We will use it later.
Angular project configuration (note I'm using version 4.4.3):
I have a previous article you may want to check that discusses keycloak http://czetsuya-tech.blogspot.com/2017/04/how-to-signin-to-keycloak-using-google.html.
After creating your client in Keycloak, download the Keycloak OIDC JSON, under the Installation tab as keycloak.json. We will use it later.
Angular project configuration (note I'm using version 4.4.3):
- Create an Angular project. I normally use Angular CLI. I assume you are already familiar with the Angular initial project configuration.
- Put your keycloak.json inside app/assets folder. This will be consumed by an angular service later.
- Install keycloak js
>npm install keycloak-js --save
- Step 3 will generate a keycloak.js inside the angular's node_modules folder. To use it we need to add an entry in .angular-cli.json. Look for scripts section and add "../node_modules/keycloak-js/dist/keycloak.js"
- Now that we have the keycloak library on our project. We need to create a service that will load it.
import { Injectable } from '@angular/core'; import { environment } from 'environments/environment'; declare var Keycloak: any; @Injectable() export class KeycloakService { static auth: any = {}; static init(): Promise<any> { let keycloakAuth: any = new Keycloak('assets/keycloak.json'); KeycloakService.auth.loggedIn = false; return new Promise((resolve, reject) => { keycloakAuth.init({ onLoad: 'check-sso' }) .success(() => { KeycloakService.auth.loggedIn = true; KeycloakService.auth.authz = keycloakAuth; KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/" + environment.keycloakRealm + "/protocol/openid-connect/logout?redirect_uri=http://localhost:4200/index.html"; resolve(); }) .error(() => { reject(); }); }); } // more methods here }
This service loads keycloak.json from the assets folder. It initializes keycloak using the 'check-sso' parameter. Why? So that angular will not redirect to keycloak's login page every time we access a page. If login is successful we store the keycloak object in keyClockService's auth variable. Note that this variable is static, so we can access it in any of our angular's components, as long as we inject the service. - Since we used check-sso parameter in keycloak, we need to manually secure the routes, which we don't want to be accessible by the general public. To do that, we need to create a guard that we can use in Routes. This is how we define the guard:
import { Injectable } from '@angular/core'; import { Router, CanActivate, CanLoad } from '@angular/router'; import { Logger } from "angular2-logger/core"; import { KeycloakService } from 'app/core/auth/keycloak.service'; @Injectable() export class AuthGuardService implements CanActivate, CanLoad { constructor(public router: Router, private keycloakService: KeycloakService, private logger: Logger) { } canActivate(): boolean { return false; } canLoad(): boolean { if (KeycloakService.auth.loggedIn && KeycloakService.auth.authz.authenticated) { this.logger.info("user has been successfully authenticated"); return true; } else { KeycloakService.login(); return false; } } }
- Now we need to declare our AuthGuardService in our providers, so the system can access it. Open your app.module.ts and add it:
AuthGuardService, { provide: HTTP_INTERCEPTORS, useClass: SecuredHttpInterceptor, multi: true },
- Then in our Routes definition (I assume you have app-routing.module.ts) we guard the Routes like this:
{ path: 'dashboard/vendor', canLoad: [AuthGuard], loadChildren: 'app/module/dashboard/vendor/vendor.module#VendorModule' }
- At this point, all our prerequisites to secure the angular app had been already fulfilled. We just need to initialize keycloak somewhere when we access the app and there is no better place to do that but in main.ts.
KeycloakService.init() .then(() => { const platform = platformBrowserDynamic(); platform.bootstrapModule(AppModule); }) .catch(() => window.location.reload());
Perfect. Now keycloak will do nothing if there is no session cookie. Otherwise, it will log in automatically and if the session is valid, redirects back to the app. - Since most angular apps access a REST API, we normally would want to add a bearer token on any request whenever we have an authenticated user. We do that by implementing the newly introduced HttpInterceptor. And this is how we do it:
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/do'; import { KeycloakService } from 'app/core/auth/keycloak.service'; @Injectable() export class SecuredHttpInterceptor implements HttpInterceptor { intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { KeycloakService.getToken(); let kcToken = KeycloakService.auth.authz.token; if (KeycloakService.auth.loggedIn && KeycloakService.auth.authz.authenticated) { request = request.clone({ setHeaders: { Authorization: 'Bearer ' + kcToken } }); } return next.handle(request); } }
- Finally, we have everything to secure our angular app.
6 comments
Hi Edward,
Thanks for this article. I am new working with angular and keycloak and I have a question. What would be the login function in the keycloakservice ?. Could you write it ?.
Thank you very much for everything.
Greetings.
Hi Nicolas,
Here's the code I used.
static login() {
KeycloakService.auth.authz.login().success( function() {
KeycloakService.auth.authz.initPromise.setSuccess();
} ).error( function() {
KeycloakService.auth.authz.initPromise.setError();
} );
}
Hi Edward,
What would be the AuthGuard in the app-routing.module.ts? AuthGuardService or something else? Could you write it?
Thank you.
Hi, does anybody know what should be in ??
Does anybody know what should be in KeycloakService.getToken(); ??
Hi @NeoJustin, check the code here: https://github.com/czetsuya/Keycloak-Angular-Auth/blob/master/src/app/core/auth/keycloak.service.ts
Post a Comment