Omar A
Omar A

Reputation: 103

Aurelia Keycloak integration

Very new to both Aurelia and keycloak and trying to get the two to work together. Using the Aurelia production seed without typescript

I am trying to follow this example that uses Angular2 keycloak-angular2

there is also an angular1 example in that repo

Steps taken so far (I have updated the code snippet with additional progress)

1) Added the bower-jspm endpoint and installed keycloak.js

2) Added a keycloak-service.js (Updated)

```
import {keycloak} from 'keycloak';
import {LogManager} from 'aurelia-framework';

let logger = LogManager.getLogger('KeycloakService');

export class KeycloakService {
  static auth = {};

  static init() { 
    let keycloakAuth = new Keycloak({
      "realm": "my-realm",
      "url": "http://localhost:8080/auth",
      "ssl-required": "external",
      "clientId": "CID"
    });

    KeycloakService.auth.loggedIn = false;

    return new Promise(function(resolve, reject) {
      keycloakAuth.init({ onLoad: 'login-required' })
        .success(() => {
          logger.debug("Init Success");
          KeycloakService.auth.loggedIn = true;
          KeycloakService.auth.authz = keycloakAuth;
          KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl  + "/realms/my-realm/tokens/logout?redirect_uri=/";
          resolve(null);
        })
       .error(() => {
          logger.debug("Init Failed");
          reject(null)
       });
    });
   }

  logout(){
    logger.debug("Logging out");
    KeycloakService.auth.loggedIn = false;
    KeycloakService.auth.authz = null;
    window.location.href = KeycloakService.auth.logoutUrl;
  }

  getToken() {
   return new Promise(function(resolve, reject) {
     if (KeycloakService.auth.authz.token) {
        logger.debug("Refreshing token");
        KeycloakService.auth.authz.updateToken(5)
          .success(function() {
            logger.debug("Token refreshed");
            resolve(KeycloakService.auth.authz.token);
          })
         .error(function() {
            logger.debug("Failed to refresh token");
            reject('Failed to refresh token');
         });
     }
   });
 }
}
```

3) In the main.js (Updated),

```
import {KeycloakService} from 'keycloak-service';
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging();


  KeycloakService.init()
    .then(() => aurelia.start().then(() => aurelia.setRoot()));
}
```

When i go to localhost:9000 , it redirects me to the login page, and allows me to login and takes me to the welcome page. Everytime i refresh the localhost:9000 page, it does not remember my previous login (even though my keycloak session is active) and forces me to login again. I am guessing this is due to onload login required. Though shouldnt it remember that i am already logged in?

After logging in, the console shows an error "keycloak.js:828 Uncaught TypeError: Cannot read property 'postMessage' of null" on checkLoginIframe.

I am not sure how to implement the route/http interceptor

I tried creating a simple class to hit a health endpoint at the server

```
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {KeycloakService} from 'keycloak-service';
import {LogManager} from 'aurelia-framework';
import 'fetch';

let logger = LogManager.getLogger('health');

@inject(HttpClient, KeycloakService)
export class Health {
  constructor(http, keycloakService) {
    http.configure(config => {
      config
      .useStandardConfiguration()
      .withBaseUrl('http://localhost:8081/api/health/')
      .withDefaults({
        headers: {
          'X-Requested-With': 'Fetch'
        }
      });
  });

    this.http = http;
    this.keycloakService = keycloakService;
  }

  activate() {
    return this.keycloakService.getToken().then(
      token => {
        // Somehow add this token to the header ...
        this.http.fetch('summary').then(response => response.json())
      }
    );
  }
}

```

However this fails with the same checkLoginIFrame issue again. Also unsure how i would inject the bearer token header at this point. Is there a better way to intercept this globally (or semi globally for certain routes) or should i create a superclass and extend all the services with it that need to send a token to the server.

I am trying to get to a point where the welcome screen is not secured, and possibly a search screen that is not secured, (so i wouldnt do onLoad: 'login-required'. I want to have a explicit link for login/signup on main page. And the rest of the navigation requires the user to be logged in

Has any one successfully achieved this and can share some code? As i said, primarily a server side dev with limited angular 1 experience and evaluating keycloak as well so pretty much uncharted waters with all this stuff

Oh and i couldnt just copy the keycloak.json from the keycloak console installation tab as is, had to change "resource" to "clientId" and "auth-server-url" to "url", is that normal behaviour?

---- UPDATE ----

so after debugging, the document.body.appendChild(iframe); does set the contentWindow on the iframe, but when the checkLoginIframe gets called after the interval expires, the contentWindow for some reason is changed to null. I guess it has something to do with when I do the init vs when aurelia is done doing its thing, i tried calling init at different points, but then the router gets in the way of things. Since i do not need to do load on init, i just do the init on configure in main.js and at least for now this issue goes away.

Using the js-console demo from keycloak as an example, i am trying to implement explicit login. The login button takes me to the keycloak login page, however on logging in, the issue i am running into now is that the aurelia router complains that `Error: Route not found: http://localhost:9000/?redirect_fragment=%2Fkcr#state=...&code=...' where kcr is the html/js route module i am using to put some buttons on the screen to test the functionality

thanks

Upvotes: 1

Views: 4605

Answers (2)

johanneslink
johanneslink

Reputation: 5341

Initializing Keycloak with

keycloakAuth.init({onLoad: 'login-required', checkLoginIframe: false})

solved the problem for me.

The reason seems to be that the checkLoginIframe element is only correctly created and added to the dom if you configure onLoad: 'check-sso'. But that's only a guess informed by looking at and debugging through keycloak.js. If my guess is correct, keycloak.js should get some better configuration error handling.

Upvotes: 1

wpenn
wpenn

Reputation: 11

See Aurelia-Keycloak.

Aurelia-Keycloak

Alpha version. An authentication plugin based on KeyCloak for Aurelia applications.

Get Started

  • Install Aurelia-Keycloak:

    jspm install aurelia-keycloak

  • Add keycloak configuration and initialization settings:

Follow Keycloak directions for creating a keycloak.json configuration file. Put this file in the same directory as your application's index.html file. Refer to the keycloak javascript adapter documentation for its initialization options and API.

  • Add plugin to your app's main.js. This example assumes your keycloak.json is in your root directory. This code will immediately cause the login screen to appear.
    .plugin('aurelia-keycloak', {initOptions:{ onLoad: 'login-required' }})
  • To defer login, use the following: .plugin('aurelia-keycloak') Then, construct a login button within your code to call the keycloak login function.
  • Rather than use a keycloak.json file, you can insert the installation config with the plugin declaration

`.plugin('aurelia-keycloak',{install:{PASTE GENERATED KEYCLOAK.JSON HERE}},initOptions:{ onLoad: 'login-required' }}

See the GITHUB for details.

Upvotes: 0

Related Questions