no

Secure Angular4 app with Keycloak

Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

Disclaimer: This is not a tutorial on how to setup an Angular^4 project, nor are we going to discuss how to setup a Keycloak server. Needless to say, you should already be familiar 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 Installation tab as keycloak.json. We will use it later.

Angular project configuration (note I'm using version 4.4.3):

  1. Create your Angular project. I normally use Angular CLI. I assume you are already familiar with Angular initial project configuration.
  2. Put your keycloak.json inside app/assets folder. This will be consume by an angular service later.
  3. Install keycloak js
    >npm install keycloak-js --save
  4. Step 3 will generate a keycloak.js inside 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"
  5. Now that we have the keycloak library on our project. We need to create the 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 assets folder. It initialize keycloak using '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.
  6. 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;
            }
        }
    
    }
    
  7. 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
     },
    
  8. 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' }
    
  9. 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 login automatically and if the session is valid, redirects back to the app.
  10. 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);
        }
    }
    
  11. Finally we have everything to secure our angular app.
Demo template is available for sale for $50. You can send payment via skype at: czetsuya@gmail.com

Related

java 6537522969773586693

Post a Comment

3 comments

Nicolas said...

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.

Edward Legaspi said...

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();
} );
}

Anonymous said...

Hi Edward,
What would be the AuthGuard in the app-routing.module.ts? AuthGuardService or something else? Could you write it?
Thank you.

item