Self hosted user management

Hi @pfiadDi, this should be possible in theory.

I am still running [email protected] on my private instance which is the last version supporting basic auth. I then have my Identity Provider (Authentik) attach basic auth credentials to the relevant requests, and most reverse proxies will also be able to inject the required headers.

For n8n version v1 you’d need to hack a bit. I have not set this up myself yet, but a while back @netroy shared a hooks file with me that should handle the log in using basic auth credentials:

const { dirname, resolve } = require('path')
const Layer = require('express/lib/router/layer')
const basicAuth = require('basic-auth')
const { issueCookie } = require(resolve(dirname(require.resolve('n8n')), 'auth/jwt'))

const basicAuthCredentials = {
  username: process.env.N8N_BASIC_AUTH_USER,
  password: process.env.N8N_BASIC_AUTH_PASSWORD,
}

const ignoreAuthRegexp = /^\/(assets|healthz|webhook)/;

module.exports = {
  n8n: {
    ready: [
      async function ({ app }, config) {
        await this.dbCollections.Settings.update(
          { key: 'userManagement.isInstanceOwnerSetUp' },
          { value: JSON.stringify(true) },
        )

        config.set('userManagement.isInstanceOwnerSetUp', true)

        const { stack } = app._router;

        if (basicAuthCredentials.username && basicAuthCredentials.password) {
          stack.unshift(new Layer('/', {
            strict: false,
            end: false
          }, async (req, res, next) => {
            if (ignoreAuthRegexp.test(req.url)) return next();
  
            const authorization = basicAuth(req);
            if (!authorization || authorization.name !== basicAuthCredentials.username || authorization.pass !== basicAuthCredentials.password) {
              res.statusCode = 401
              res.setHeader('WWW-Authenticate', 'Basic realm="n8n"')
              res.end('Access denied')
            } else {
              next()
            }
          }))
        }

        const index = stack.findIndex((l) => l.name === 'cookieParser')
        stack.splice(index + 1, 0, new Layer('/', {
          strict: false,
          end: false
        }, async (req, res, next) => {
          if (!req.cookies?.['n8n-auth']) {
            const owner = await this.dbCollections.User.findOneBy({
              globalRole: {
                name: 'owner',
                scope: 'global',
              },
            })
            issueCookie(res, owner)
          }

          next()
        }))

        console.log("UM Disabled")
      },
    ],
  },
}

Perhaps you want to give this a go and let us know how it goes? The n8n.ready hook would be a backend hook.

1 Like