Official Plugins (Kuzzle v2.x)
OpenID v2.x
2

Kuzzle Auth OpenID Plugin #

This is the official Kuzzle OpenID authentication plugin.

This plugins allows you to configure multiple strategies to allow users to authenticate themselves from your authentication server using the OpenID protocol.

Kuzzle #

Kuzzle is an open-source backend that includes a scalable server, a multiprotocol API, an administration console and a set of plugins that provide advanced functionalities like real-time pub/sub, blazing fast search and geofencing.

Compatibility matrix #

Kuzzle VersionPlugin Version
>= 2.17.31.x.x
>= 2.42.02.x.x

Installation #

npm install @kuzzleio/kuzzle-plugin-auth-openid

Then in your application:

import { Backend, JSONObject } from 'kuzzle';
import { PluginAuthOpenID } from '@kuzzleio/kuzzle-plugin-auth-openid';

const app = new Backend('kuzzle');

class PluginAuthKeycloak extends PluginAuthOpenID {
  constructor () {
    super('keycloak'); // Create a new strategy named "keycloak"
  }

  // Required method that should return which profiles to give to the user
  async extractor (claims: JSONObject): Promise<ExtractedUserInfo> {
    return {
      profiles: ['kuzzleprofile-default'] // Give the user the profile named "default"
      userdata: {} // Add / Update any informations in the user custom data
    };
  }
}

app.plugin.use(new PluginAuthKeycloak()); // Add the plugin to your application

app.start()
  .then(() => {
    app.log.info('Application started');
  })
  .catch(console.error);

Configure a Strategy #

To configure a strategy you need to add a plugins.auth-keycloak (because the name of your pluginClass is "AuthKeycloak in this case") section in your Kuzzle configuration file. (usually .kuzzlerc)

Example

{
  "plugins": {
    "auth-keycloak": {
      "issuer": "https://sso.kuzzle.io/realms/kuzzle",
      "clientId": "kuzzle-backend",
      "clientSecret": "fBw6BYEG3TXnq...AFHzIej5D87C",
      "callbackURL": ["http://localhost:3000/"]
    }
  }
}

You can try your strategy by visiting http://<kuzzle host>/_login/keycloak

OpenID Protocol Overview #

Key Components #

  • End User: The person trying to access a service
  • Relying Party (RP): The application/website requesting authentication
  • OpenID Provider (OP): The identity provider that authenticates the user
  • ID Token: A JWT (JSON Web Token) containing user information

Flow description #

  1. User attempts to access a protected resource on the Relying Party
  2. RP redirects user to the OpenID Provider
  3. User authenticates with the OP
  4. OP generates an ID Token and authorization code
  5. User is redirected back to RP with the tokens
  6. RP validates the tokens and grants access

User Profile Attribution #

Once a user connects to Kuzzle using one of the provided strategies, the user will be granted with each profiles returned by the ProfileExtractor that is starting with kuzzleprofile-. Each entry not starting with kuzzleprofile- will be ignored.

Returned profiles starting with kuzzleprofile- should always match Kuzzle profiles. As an example if you have a profile named visitor, the ProfileExtractor should return a list of profiles containing kuzzleprofile-visitor.

⚠️ If its a newly created user, it will apply extrated profiles to the user.

⚠️ If the user already exists, it will always apply value extracted by the ProfileExtractor, else it will keep what's already available.

It is possible for you to override the default behavior of the ProfileExtractor by implementing your own extractor function.

Create custom OpenID Strategy #

import { Backend, JSONObject } from 'kuzzle';
import { PluginAuthOpenID } from '@kuzzleio/kuzzle-plugin-auth-openid';

const app = new Backend('kuzzle-plugin-openid');

// Implement custom authentication strategy using the OpenID protocol
class PluginAuthKeycloak extends PluginAuthOpenID {
  constructor () {
    super('keycloak'); // Create a new strategy named "keycloak"
  }

  // Required method that should return which profiles to give to the user
  async extractor (claims: JSONObject): Promise<ExtractedUserInfo> {
    return {
      profiles: ['kuzzleprofile-default'] // Give the user the profile named "default"
      userdata: {} // Add / Update any informations in the user custom data
    };
  }
}

app.plugin.use(new PluginAuthKeycloak());

app.start().then(() => {
  console.log('Application started');
});

Login using your OpenID strategy #

Login using the authentication server interface #

You can send a request to <kuzzle host>:<port>/_login/<strategy>, Or use the SDK to trigger the login.

⚠️ The SDK JS is (Still in 7.15.0) not supporting connection trough the OpenID strategy, when using the sdk.auth.login you will need to use the sdk.query method in order to trigger a login without any issues.

// The first call to the route, will create the redirectURI and a sessionId
const response = await kuzzle.query({
  controller: "auth",
  action: "login",
  strategy: "keycloak",
});

// Result of the first call
{
  headers: {
    keycloak: <uniqSessionId>,
    location: "https://<keycloak host>:<port>/realms/<realm>/protocol/openid-connect/auth?client_id=<client_id>&redirect_uri=http://<kuzzle host>:<port>/_login/keycloak&response_type=code&scope=openid&state=<sessionId>"
  }
}

// Be sure to save the sessionId in the localStorage or somewhere else that you will be able to retrieve once the user is back from the authentication server

// The second call to the route, validationGrant process
// awaited parameters are the sessionId and the callbackUrl (the url where the user will be redirected after the authentication)
const response = await kuzzle.query({
  controller: "auth",
  action: "login",
  strategy: "keycloak",
  body: {
    sessionId: <uniqSessionId>,
    callbackUrl: "https://<keycloak host>:<port>/realms/<realm>/protocol/openid-connect/auth?client_id=<client_id>&redirect_uri=http://<kuzzle host>:<port>/_login/keycloak&response_type=code&scope=openid&state=<sessionId>",
  },
});

If the login is successful, the response object will contain a Kuzzle JWT token that can be used to authenticate future requests.

Development #

In order to develop the plugin, you can use the following commands:

kourou app:scaffold sanbox

In your package.json you should add a link to your plugin:

{
  "dependencies": {
    "@kuzzleio/kuzzle-plugin-auth-openid": "file:../kuzzle-plugin-auth-openid"
  }
}

Then you can modify the MyApplication.ts file to add your custom code

Remember to update the .kuzzlerc file to add the plugin configuration.

import { Backend, JSONObject } from 'kuzzle';
import { PrometheusPlugin } from 'kuzzle-plugin-prometheus';
import PluginAuthOpenID, {
  ExtractedUserInfo,
} from '@kuzzleio/kuzzle-plugin-auth-openid';

export type MyApplicationConfig = {
  someValue: string;

  another: {
    value: number;
  };
};

class PluginAuthKeycloak extends PluginAuthOpenID {
  public app: MyApplication;

  constructor(app: MyApplication) {
    super('keycloak');
    this.app = app;
  }

  async extractor(claims: JSONObject): Promise<ExtractedUserInfo> {
    this.app.log.info(claims);

    return {
      profiles: ['kuzzleprofile-default'],
      userdata: {},
    };
  }
}

export class MyApplication extends Backend {
  public configuration: MyApplicationConfig;
  private prometheusPlugin = new PrometheusPlugin();
  private keycloakPlugin = new PluginAuthKeycloak(this);

  get appConfig() {
    return this.config.content.application as MyApplicationConfig;
  }

  constructor(config?: MyApplicationConfig) {
    super('my-application');

    if (config) {
      this.configuration = config;
    } else {
      this.configuration = this.config.content
        .application as MyApplicationConfig;
    }

    this.plugin.use(this.prometheusPlugin);
    this.plugin.use(this.keycloakPlugin);
  }

  async start() {
    await super.start();

    this.log.info('Application started');
  }
}

You then can start your application using the following command:

npm run dev