Oauth/OpenId Login with Keycloak based on roles

Hi Everyone,

I’m integrating Keycloak authentication into Node-RED using passport-keycloak-oauth2-oidc. The authentication flow is successful, and I can see the correct user profile and roles returned from Keycloak. However, after authentication, the app redirects back to the login screen instead of granting access to the Node-RED editor.

What I have so far:

  • I’ve followed the structure and logic used in the node-red-auth-github example.
  • When I use the static users option (i.e. hardcoding users and permissions in settings.js), everything works as expected — users are authenticated and redirected correctly to the editor.
  • Now, I want to fetch roles dynamically from Keycloak and assign permissions based on those roles instead of using the static users option.

Keycloak Setup:

  • Realm: MyRealm
  • Client ID: nodered-client
  • Roles assigned in Keycloak: nodered-editor, nodered-viewer
  • I confirmed that roles like nodered-editor are returned in the profile inside resource_access.

My Passport Strategy:

var path = require("path");
var oauth2Strategy = require("passport-keycloak-oauth2-oidc");

var requiredOptions = [
    'clientID',
    'clientSecret',
    'realm',
    'authServerURL',
    'baseURL'
];

module.exports = function (opts) {
    // Validate required options
    for (var i = 0; i < requiredOptions.length; i++) {
        if (!opts.hasOwnProperty(requiredOptions[i])) {
            throw new Error("Missing auth option: " + requiredOptions[i]);
        }
    }

    // Construct callback URL
    var callbackURL = opts.baseURL +
        ((opts.baseURL[opts.baseURL.length - 1] === "/") ? "" : "/") +
        "auth/strategy/callback";

    // Define adminAuth config
    var adminAuth = {
        type: "strategy",
        strategy: {
            name: "keycloak",
            label: "Sign in with Keycloak",
            icon: "fa-lock",
            strategy: oauth2Strategy.Strategy,
            options: {
                clientID: opts.clientID,
                clientSecret: opts.clientSecret,
                realm: opts.realm,
                authServerURL: opts.authServerURL,
                callbackURL: callbackURL,
                publicClient: 'false',
                scope: "openid profile email",
                sslRequired: 'external',
                verify: function (accessToken, refreshToken, profile, done) {
                    console.log("Keycloak profile:", profile);

                    const roles = profile.roles?.resource_access?.[opts.clientID]?.roles || [];
                    console.log("Roles for the user:", roles);

                    let permissions = [];
                    if (roles.includes("nodered-editor")) {
                        permissions = ["*"]; // Full access
                    } else if (roles.includes("nodered-viewer")) {
                        permissions = ["flows.read"]; // Read-only
                    }

                    const user = {username: profile.username || profile.preferred_username || profile.sub,
                          permissions: permissions,email: profile.email || null,};

                    return done(null, user);
                    console.log("Authenticated user:", user);
                }
            }
        }
    };

    // Optional default permission
    if (opts.hasOwnProperty("default")) {
        adminAuth.default = opts.default;
    }

    return adminAuth;
};

adminAuth in settings.js:

adminAuth: require('mynodered-plugin')({
clientID: "nodered-client",
clientSecret: "secret",
realm: "MyRealm",
authServerURL: "https://auth-example.com",
baseURL: "https://example.com/"

}),

My user logs:

Any guidance or examples that could help get role-based dynamic authentication working with Keycloak would be greatly appreciated!