simov / grant

OAuth Proxy
MIT License
4.08k stars 257 forks source link

How to configure dynamic providers in express? #254

Closed chrislmar closed 3 years ago

chrislmar commented 3 years ago

Hi,

I'm working on a project where users must be able to configure custom OIDC providers. I'm trying to figure out how to use grant with custom provider configurations without hardcoding the providers.

I've tried to follow some examples for configuration with dynamic state, but I haven't succeeded yet. All of these examples have a provider defined when the grant middleware is initialized, and the custom middleware (which runs before grant) is able to override some keys dynamically. https://github.com/simov/grant/blob/master/examples/dynamic-state/express.js https://github.com/simov/grant/issues/224 https://github.com/simov/grant/issues/110

But I can't find examples that add an entire new provider. Does grant support adding new providers dynamically (e.g. from the database) while the server is running? Here's some sample code of what I've tried.

With this code, I end up hitting the "missing or misconfigured provider" error from http://localhost:4000/?error=Grant%3A%20missing%20or%20misconfigured%20provider. That happens when I initiate login at http://localhost:4000/connect/exampleProvider and also when I initiate login at my test provider.

import grant from 'grant'
...

export const app = express()
app.use(session({ secret: 'grant', saveUninitialized: true, resave: false }))
app.use('/connect/exampleProvider', (req, res, next) => {
    // I will eventually read the config dynamically instead of this
    const config = {
        exampleProvider: {
            key: '...',
            secret: '...',
            oauth: 2,
            access_url: '...',
            authorize_url: '...',
            logout_url: '...',
            callback: '...',
        }
    }
    res.locals.grant = config
    next()
})

app.use(
    grant.express({
        defaults: {
            origin: 'http://localhost:4000',
            transport: 'state',
            state: true,
            nonce: true,
            scope: ['openid', 'profile']
            scope_delimiter: ' ',
            response: ['tokens', 'jwt', 'raw', 'profile'],
            pkce: true,
        },
    })
)

app.get('/connect/:provider/callback', (req, res) => {
        // access res.locals.grant.response
})

Thanks in advance!

simov commented 3 years ago

You need to set dynamic: true inside defaults. That configuration have a special meaning there to allow dynamic providers. It's documented here. I know the documentation says that the dynamic configuration is being used to configure Dynamic HTTP Overrides only, but this is one of the very few edge cases of configuration that have a special meaning.

Let me know if it works for you.

chrislmar commented 3 years ago

Thank you for the tip! I had to make two changes:

  1. set dynamic: true inside defaults.
  2. use dynamic as the key for my dynamic config, not the name of my custom provider. This makes sense now, but I was stuck before thinking that the key needed to match the provider name in the URL path.
app.use('/connect/:provider', (req, res, next) => {
    const config = {
        dynamic: { // DIFF: use key 'dynamic' instead of provider name
            key: '...',
            secret: '...',
            ...
        }
    }
    res.locals.grant = config
    next()
})

app.use(
    grant.express({
        defaults: {
            dynamic: true // DIFF: added this
            origin: 'http://localhost:4000',
            transport: 'state',
            ....
        },
    })
)