simov / grant

OAuth Proxy
MIT License
4.08k stars 257 forks source link

Alternating Domains #295

Closed dbarrosop closed 10 months ago

dbarrosop commented 10 months ago

We have a situation where our authentication service may have different domains for transition purposes (i.e. migrating from one domain to another and having to support both while users update their mobile apps). We tried setting the origin dynamically like this (using expressjs):

  .all(`${OAUTH_ROUTE}/:provider`, (req, res, next) => {
    res.locals.grant = {dynamic: {
        origin: `${req.protocol}://${req.headers.host}${prefix}`},
    };
    next();
  })

Which seems to work, however, we are using the code authorization flow and when the callback is called and grant tries to exchange the code for the access token things break. I think the issue is that at that point the redirect_uri sent to the identity provider isn't matching the original one used.

Is there any better way to set configuration dynamically? Or do you have any tips or pointers on how this could be solved?

dbarrosop commented 10 months ago

Nevermind, it was an ordering issue.

simov commented 10 months ago

I'm glad it worked out for you, I was about to have a look as well. Let me know if you have any other issues :+1:

simov commented 10 months ago

@dbarrosop I would be curious to know how you got it working by overriding the origin. Maybe your setup is a bit different than mine, but here is what I did to test this on my end:

  1. I took the dynamic state override example from here https://github.com/simov/grant/blob/master/examples/dynamic-state/express.js and I updated it like this:
var express = require('express')
var session = require('express-session')
var grant = require('../../').express()

express()
  .use(session({secret: 'grant', saveUninitialized: true, resave: false}))
  .use('/connect/:provider', (req, res, next) => {
    res.locals.grant = {dynamic: {
      redirect_uri: [
        `https://${req.headers.host}`,
        'connect',
        req.params.provider,
        'callback'
      ].join('/')
    }}
    next()
  })
  .use(grant(require('./config.json')))
  .get('/hello', (req, res) => {
    res.end(JSON.stringify(req.session.grant.response, null, 2))
  })
  .listen(3000)
{
  "defaults": {
    "origin": "http://localhost:3000",
    "transport": "session"
  },
  "google": {
    "key": "APP_ID",
    "secret": "APP_SECRET",
    "scope": ["openid"],
    "callback": "/hello"
  }
}
  1. Then I added two more redirect URIs to my Google app

  2. I added those two domains to resolve to localhost in my hosts file

  3. And finally I created two vhost files using Nginx to forward everything to my underlying app listening on localhost:3000 including the Host header as well

The reason why I am curious is because the origin is merely used to construct the redirect_uri as explained here, but overriding just the origin won't re-construct the redirect_uri

dbarrosop commented 10 months ago

because the origin is merely used to construct the redirect_uri as explained here, but overriding just the origin won't re-construct the redirect_uri

where does it say that? I don't think the linked section says that (although it would confirm some behavior we saw).

So our setup is basically as follows, we start grant with the following defaults:

      defaults: {
        // origin: ENV.AUTH_SERVER_URL,
        prefix: OAUTH_ROUTE,
        transport: 'session',
        scope: ['email', 'profile'],
        response: ['tokens', 'email', 'profile', 'jwt'],
      },

Notice we had to comment origin, if we specify origin in the defaults our setup doesn't work (which may confirm your point above, although I don't think it is stated in the link).

Then we do (this is our current POC, we aren't done, but this works):

  .use((req, res, next) => {
    res.locals.grant = {dynamic: {
        origin: `${req.protocol}://${req.headers.host}/v1`},
    };
    next();
  })
  .use(grant.express(grantConfig))

We tried with the redirect_uri same as you but it didn't work for us despite having set dynamic: true in the defaults and dynamic: ['redirect_uri'] in the providers. The only thing that seemed to work was removing origin from defaults and setting it in a middleware.

Also, keep in mind that if you change the redirect_url you need to do it both in the authentication endpoint and also in the callback, otherwise you will break the authorization code flow (that's why we are using a middleware).

simov commented 10 months ago

Ok, I misread my own code a little bit, that transformation is being applied always so that's why configuring the origin dynamically generates the redirect_uri as well.

The reason why having the origin set initially does not work is because in that case you already have a redirect_uri pre-generated out of it and so that takes precedence when you set the origin dynamically. This is also the reason why me configuring the redirect_uri dynamically works always even if I had some origin set initially. But your approach is a really cool trick too.

From here: https://github.com/simov/grant#misc-redirect-uri

The origin and the prefix configuration is used to generate the correct redirect_uri that Grant expects:

and

Explicitly specifying the redirect_uri overrides the one generated by default.