feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.06k stars 752 forks source link

[v4] oauth authentication faild with error=Grant session #1515

Closed fadiquader closed 5 years ago

fadiquader commented 5 years ago

Google oauth was working properly when I was using v3, but after I migrated to v4, I was unable to authenticate anymore. After I authenticate using Google it redirects to https://exampe.com/oauth/connect/google/callback?code=..... and displays the following error on the page:

error=Grant%3A%20missing%20session%20or%20misconfigured%20provider

default.json

{
..
"authentication": {
    "secret": "JWT_SECRET",
    "entity": "user",
    "service": "users",
    "authStrategies": [
      "jwt",
      "local"
    ],
    "path": "/authentication",
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "1d"
    },
    "local": {
      "entity": "user",
      "usernameField": "email",
      "passwordField": "password"
    },
    "oauth": {
      "redirect": "REDIRECT",
      "defaults": {
        "protocol": "PROTOCOL",
        "host": "HOST"
      },
      "google": {
        "key": "GOOGLE_AUTH_CLIENT_ID",
        "secret": "GOOGLE_AUTH_CLIENT_SECRET",
        "scope": ["profile openid email"]
      },
      "facebook": {
        "key": "FB_AUTH_CLIENT_ID",
        "secret": "FB_AUTH_CLIENT_SECRET",
        "profileFields": [
          "id",
          "displayName",
          "first_name",
          "email",
          "gender",
          "profileUrl",
          "birthday",
          "picture",
          "permissions"
        ]
      }
    }
  },
..
}

authentication.js

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const authenticationOauth = require('@feathersjs/authentication-oauth');
const { express: oauth, OAuthStrategy } = authenticationOauth

module.exports = app => {
  const config = app.get('authentication');
  app.set('authentication', config)
  const authentication = new AuthenticationService(app, 'authentication');
  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());
  authentication.register('google', new OAuthStrategy())
  authentication.register('facebook', new OAuthStrategy())
  app.use('/authentication', authentication);
  app.configure(oauth());
};

app.js

..
app.configure(authentication);
..

Thanks in advance for any advice!

daffl commented 5 years ago

I was just able to test this successfully with the current feathers-chat. It looks like the scope has to be set to [ "email", "profile", "openid" ]. If that works it makes probably sense to add it to the generator.

daffl commented 5 years ago

Also, oAuth host and protocol don't need to be set since it will usually be inferred from the global host configuration and NODE_ENV.

fadiquader commented 5 years ago

@daffl I created new creds with feathers-chat example, and it worked! Thank you so much for your help.

KidkArolis commented 5 years ago

@daffl Small note on host

In my experience the production oauth callback URL usually isn't inferred correctly, because feathers includes the port: ${app.get('host')}:${app.get('port')}, but if you're hosting behind a proxy like nginx or in something like heroku, the port is internal only and should not be part of the public callback url.

fadiquader commented 5 years ago

@KidkArolis You mentioned a good point, that's why i manually set the "host", "protocol" in the environment variables.

daffl commented 5 years ago

Hm... good point actually. It might make sense to leave the port out in production. You can always override it locally. I'm wondering what went wrong in your original setup. I also noticed that Grant doesn't give a lot of information here, it might be worth digging into.

danlupascu commented 4 years ago

I am getting the same error with both facebook and google.

Here's my config:

{
  "host": "localhost",
  "port": 3030,
  "public": "../public/",
  "paginate": {
    "default": 10,
    "max": 200
  },
  "authentication": {
    "entity": "user",
    "service": "users",
    "secret": "secret",
    "authStrategies": [
      "jwt",
      "local"
    ],
    "jwtOptions": {
      "header": {
        "typ": "access"
      },
      "audience": "https://yourdomain.com",
      "issuer": "feathers",
      "algorithm": "HS256",
      "expiresIn": "30d"
    },
    "local": {
      "usernameField": "email",
      "passwordField": "password"
    },
    "oauth": {
      "redirect": "OAUTH_REDIRECT_URL",
      "facebook": {
        "key": "FACEBOOK_APP_ID",
        "secret": "FACEBOOK_APP_SECRET",
        "scope": ["email, public_profile"]
      },
      "google": {
        "key": "GOOGLE_PROJECT_ID",
        "secret": "GOOGLE_PROJECT_SECRET",
        "scope": [ "email", "profile", "openid" ]
      }
    }
  },
  "mongodb": "MONGODB_URL"
}

And this is my authentication service

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth, OAuthStrategy } = require('@feathersjs/authentication-oauth');
const axios = require('axios');

class FacebookStrategy extends OAuthStrategy {
  async getProfile (authResult) {
    // This is the oAuth access token that can be used
    // for Facebook API requests as the Bearer token
    const accessToken = authResult.access_token;

    const { data } = await axios.get('https://graph.facebook.com/me', {
      headers: {
        authorization: `Bearer ${accessToken}`
      },
      params: {
        // There are
        fields: 'id,name,email,picture'
      }
    });

    return data;
  }

  async getEntityData(profile) {
    // `profile` is the data returned by getProfile
    const baseData = await super.getEntityData(profile);

    return {
      ...baseData,
      isVerified: true,
      displayName: profile.name,
      name:  profile.name,
      email: profile.email
    };
  }
}

class GoogleStrategy extends OAuthStrategy {
  async getEntityData(profile) {

    // this will set 'googleId'
    const baseData = await super.getEntityData(profile);

    // this will grab the picture and email address of the Google profile
    return {
      ...baseData,
      isVerified: true,
      profilePicture: profile.picture,
      email: profile.email
    };
  }
}

module.exports = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());
  authentication.register('facebook', new FacebookStrategy());
  authentication.register('google', new GoogleStrategy());

  app.use('/authentication', authentication);
  app.configure(expressOauth());
};

It doesn't matter whether I accept or cancel the login request, it sends me to this url: http://localhost:3030/oauth/connect/google/callback?code=4%2FtQF8-vzUPx56ArlBz1WJYz3o5v_eQG0xSDZ87Xk41lA6K7cZXkk3gnpyGo5rnAwNVILxzQo_l6BHltHizyE7scs&scope=email%20profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid&authuser=0&session_state=31a9e2ae84bacfd25937d816f078ac409e57682e..3240&prompt=consent#

and there's always the same error: error=Grant%3A%20missing%20session%20or%20misconfigured%20provider

I updated all the libraries to the latest versions and still it's not working.

However, everything seems to be working fine when I use the feathers-chat app.

I tracked down the grant callback endpoint and I just wanted to see what are the values of the session and provider.

In my app, both of them were empty objects {}, and this is why grant throws that error.

In the feathers-chat app, this is how they look like:


session { provider: 'facebook',
  response: { error: { error: [Object] } } }
provider { authorize_url: 'https://www.facebook.com/dialog/oauth',
  access_url: 'https://graph.facebook.com/oauth/access_token',
  oauth: 2,
  path: '/oauth',
  host: 'localhost:3030',
  protocol: 'http',
  transport: 'session',
  key: 'my_fb_app_key',
  secret: 'my_fb_app_secret',
  callback: 'http://localhost:3030/oauth/facebook/authenticate',
  redirect_uri: 'http://localhost:3030/oauth/facebook/callback',
  name: 'facebook',
  facebook: 

The `session` is `req.session.grant`. In my app, `req.session.grant` is not defined, while in the feathers-chat app,  `req.session.grant` is `{ provider: 'facebook' }`

I'd be very happy to track it further, but I'm not familiar with feathers or feathers authentication. Can someone help me, please?
edwardsmarkf commented 4 years ago

this might possibly help you. i had a similar issue and dealt directly with the author of grant. here is a future recipe:

https://github.com/feathersjs/docs/pull/1379

note the extra lines in the config file. i needed to have much more control over my callbacks.

Thank you,

Mark Edwards

On Mon, Nov 18, 2019 at 3:16 PM Dan Lupascu notifications@github.com wrote:

I am getting the same error with both facebook and google.

Here's my config:

{ "host": "localhost", "port": 3030, "public": "../public/", "paginate": { "default": 10, "max": 200 }, "authentication": { "entity": "user", "service": "users", "secret": "secret", "authStrategies": [ "jwt", "local" ], "jwtOptions": { "header": { "typ": "access" }, "audience": "https://yourdomain.com", "issuer": "feathers", "algorithm": "HS256", "expiresIn": "30d" }, "local": { "usernameField": "email", "passwordField": "password" }, "oauth": { "redirect": "OAUTH_REDIRECT_URL", "facebook": { "key": "FACEBOOK_APP_ID", "secret": "FACEBOOK_APP_SECRET" }, "google": { "key": "GOOGLE_PROJECT_ID", "secret": "GOOGLE_PROJECT_SECRET", "scope": [ "email", "profile", "openid" ] } } }, "mongodb": "MONGODB_URL" }

And this is my authentication service

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication'); const { LocalStrategy } = require('@feathersjs/authentication-local'); const { expressOauth, OAuthStrategy } = require('@feathersjs/authentication-oauth'); const axios = require('axios');

class FacebookStrategy extends OAuthStrategy { async getProfile (authResult) { // This is the oAuth access token that can be used // for Facebook API requests as the Bearer token const accessToken = authResult.access_token;

const { data } = await axios.get('https://graph.facebook.com/me', {
  headers: {
    authorization: `Bearer ${accessToken}`
  },
  params: {
    // There are
    fields: 'id,name,email,picture'
  }
});

return data;

}

async getEntityData(profile) { // profile is the data returned by getProfile const baseData = await super.getEntityData(profile);

return {
  ...baseData,
  displayName: profile.name,
  name:  profile.name,
  email: profile.email
};

} }

module.exports = app => { const authentication = new AuthenticationService(app);

authentication.register('jwt', new JWTStrategy()); authentication.register('local', new LocalStrategy()); authentication.register('facebook', new FacebookStrategy()); authentication.register('google', new OAuthStrategy());

app.use('/authentication', authentication); app.configure(expressOauth()); };

It doesn't matter whether I accept or cancel the login request, it sends me to this url: http://localhost:3030/oauth/connect/google/callback?code=4%2FtQF8-vzUPx56ArlBz1WJYz3o5v_eQG0xSDZ87Xk41lA6K7cZXkk3gnpyGo5rnAwNVILxzQo_l6BHltHizyE7scs&scope=email%20profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid&authuser=0&session_state=31a9e2ae84bacfd25937d816f078ac409e57682e..3240&prompt=consent# and there's always the same error: error=Grant%3A%20missing%20session%20or%20misconfigured%20provider

I updated all the libraries to the latest versions and still it's not working.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/feathersjs/feathers/issues/1515?email_source=notifications&email_token=AAWJ3YS2LMYYHJOWRZEQBSLQUMH4PA5CNFSM4INR2PF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEMDLYY#issuecomment-555234787, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWJ3YXYLL5TVPBFREJ7ILLQUMH4PANCNFSM4INR2PFQ .

danlupascu commented 4 years ago

this might possibly help you. i had a similar issue and dealt directly with the author of grant. here is a future recipe: feathersjs/docs#1379 note the extra lines in the config file. i needed to have much more control over my callbacks. Thank you, Mark Edwards

Hey. Which extra lines have you added in the config file? I see that you have "redirect_uri" and "callback" properties for each provider but I already tried that and it's still not working.

daffl commented 4 years ago

If you have a look at the Google oAuth cookbook entry you can see that you have to set the nonce option in the the configuration if you are requesting the email scope.

danlupascu commented 4 years ago

I just tried with nonce and it's still not working.

Here's the new config:

"oauth": {
      "redirect": "/",
      "facebook": {
        "key": "FACEBOOK_APP_ID",
        "secret": "FACEBOOK_APP_SECRET",
        "scope": ["email, public_profile"]
      },
      "google": {
        "key": "GOOGLE_PROJECT_ID",
        "secret": "GOOGLE_PROJECT_SECRET",
        "scope": [ "email", "profile", "openid" ],
        "nonce": true
      }
    }
edwardsmarkf commented 4 years ago

the author of Grant suggested to me i first get the Grant example working standalone, which solved my issue.

Thank you,

Mark Edwards

On Mon, Nov 18, 2019 at 6:47 PM Dan Lupascu notifications@github.com wrote:

I just tried with nonce and it's still not working.

Here's the new config:

"oauth": { "redirect": "/", "facebook": { "key": "FACEBOOK_APP_ID", "secret": "FACEBOOK_APP_SECRET", "scope": ["email, public_profile"] }, "google": { "key": "GOOGLE_PROJECT_ID", "secret": "GOOGLE_PROJECT_SECRET", "scope": [ "email", "profile", "openid" ], "nonce": true } }

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/feathersjs/feathers/issues/1515?email_source=notifications&email_token=AAWJ3YRKP4WFPBTBKBBPI6DQUNATNA5CNFSM4INR2PF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEEMRXXQ#issuecomment-555293662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWJ3YUBVHWBYA7LDJTKJDTQUNATNANCNFSM4INR2PFQ .

daffl commented 4 years ago

That entry also says that:

The scope value must begin with the string openid and then include profile or email or both

So your configuration should be

"scope": [ "openid", "email", "profile" ], 
danlupascu commented 4 years ago

That entry also says that:

The scope value must begin with the string openid and then include profile or email or both

So your configuration should be

"scope": [ "openid", "email", "profile" ], 

Still not working.

Note that I'm getting the same result, no matter the provider. So, both of the callbacks (facebook and google) return the same error.

By the way, the grant configuration looks fine, this is what I get when I display the grant configuration after the app is configured (console.info(app.get('grant'));)

{ defaults:
   { path: '/oauth',
     host: 'localhost:3030',
     protocol: 'http',
     transport: 'session' },
  facebook:
   { authorize_url: 'https://www.facebook.com/dialog/oauth',
     access_url: 'https://graph.facebook.com/oauth/access_token',
     oauth: 2,
     path: '/oauth',
     host: 'localhost:3030',
     protocol: 'http',
     transport: 'session',
     key: 'key',
     secret: 'secret',
     callback: 'http://localhost:3030/oauth/facebook/authenticate',
     redirect_uri: 'http://localhost:3030/oauth/facebook/callback',
     name: 'facebook',
     facebook: true },
  google:
   { authorize_url: 'https://accounts.google.com/o/oauth2/auth',
     access_url: 'https://accounts.google.com/o/oauth2/token',
     oauth: 2,
     scope_delimiter: ' ',
     custom_parameters:
      [ 'access_type',
        'approval_prompt',
        'login_hint',
        'include_granted_scopes',
        'prompt',
        'display',
        'hd' ],
     path: '/oauth',
     host: 'localhost:3030',
     protocol: 'http',
     transport: 'session',
     key: 'key',
     scope: 'openid email profile',
     nonce: true,
     callback: 'http://localhost:3030/oauth/google/authenticate',
     redirect_uri: 'http://localhost:3030/oauth/google/callback',
     name: 'google',
     google: true } }
danlupascu commented 4 years ago

It seems that I managed to make it work

I really don't know what was the problem though ... at first I thought that it was because of the redirect property but if it still works even if I change it to how it was in the previous config.

Here's my current config:

"oauth": {
      "redirect": "OAUTH_REDIRECT_URL",
      "facebook": {
        "key": "FACEBOOK_APP_ID",
        "secret": "FACEBOOK_APP_SECRET",
        "scope": ["email", "public_profile"],
        "redirect_uri": "FACEBOOK_REDIRECT_URI",
        "callback": "FACEBOOK_CALLBACK"
      },
      "google": {
        "key": "GOOGLE_PROJECT_ID",
        "secret": "GOOGLE_PROJECT_SECRET",
        "scope": ["openid", "email", "profile"],
        "redirect_uri": "GOOGLE_REDIRECT_URI",
        "callback": "GOOGLE_CALLBACK",
        "nonce": true
      }
    }

Note that I had to set the redirect_uri and callback for each provider (thank you @edwardsmarkf ) because in production it was still redirecting me to localhost:port.

Really, it's very strange and I'd investigate it further but I already spent one day on this and I'll just let it go.

daffl commented 4 years ago

Redirect URLs should be inferred from the top level host setting in the configuration. That's what I've been meaning to look into. @edwardsmarkf, @danlupascu have either of you updated the host setting in production.json? It's set to <name>-app.feathersjs.com by default.

danlupascu commented 4 years ago

My production host is localhost. However, when the redirect_uri or callback are created, the port is also added to the url, but I don't have a port in production. What should I change it to? Now it's 3030.

foo4foo commented 4 years ago

It seems that I managed to make it work

I really don't know what was the problem though ... at first I thought that it was because of the redirect property but if it still works even if I change it to how it was in the previous config.

Here's my current config:

"oauth": {
      "redirect": "OAUTH_REDIRECT_URL",
      "facebook": {
        "key": "FACEBOOK_APP_ID",
        "secret": "FACEBOOK_APP_SECRET",
        "scope": ["email", "public_profile"],
        "redirect_uri": "FACEBOOK_REDIRECT_URI",
        "callback": "FACEBOOK_CALLBACK"
      },
      "google": {
        "key": "GOOGLE_PROJECT_ID",
        "secret": "GOOGLE_PROJECT_SECRET",
        "scope": ["openid", "email", "profile"],
        "redirect_uri": "GOOGLE_REDIRECT_URI",
        "callback": "GOOGLE_CALLBACK",
        "nonce": true
      }
    }

Note that I had to set the redirect_uri and callback for each provider (thank you @edwardsmarkf ) because in production it was still redirecting me to localhost:port.

Really, it's very strange and I'd investigate it further but I already spent one day on this and I'll just let it go.

Do you inject variables into .json ? I'm currently trying to find a solution to inject production variables into config. We don't want to store production keys in .json files on our repository.

Curently, I'm using a script which reads ENV vars, updates json and overwrites the file.

Is there another solution ?

seonglae commented 2 years ago
{
      "google": {
        "key": "**",
        "secret": "**",
        "scope": ["email", "profile", "openid"],
        "nonce": true
      },
      "github": {
        "key": "**",
        "secret": "**"
      }
}

My config works in localhost but not working in my domain account.seongland.com

{
   "redirect_uri": "https://account.seongland.com/oauth/google/callback",
   "callback": "/oauth/google/authenticate"
}
        "subdomain": "account",

I tested to add this configs, but both did not worked (or added together)

/oauth/connect/github or google/callback?code=** this page shows error=Grant%3A%20missing%20session%20or%20misconfigured%20provider

Error image

My localhost config is working but I can't configure why my production configuration is not working The only difference is host, key, secret, port

Is there any information related to this issue?

yonatann commented 1 year ago

I am having the same problem as @seonglae If anyone found a solution, please let me know

king87515 commented 1 year ago

I am also having the same problem as @seonglae @yonatann If anyone found a solution, please let me know

JahnoelRondon commented 8 months ago

Good lord why is this still a hastle after so many years lol, Im having the same issue after making the application live to production in the google console and after the consent screen I just get sent this. image

this is the url it tried to redirect to. https://aquaprime-backend.up.railway.app/auth/google/oauth2callback?code=4%2F0AeaYSHAsyJAWUUEUm_90lORoDR4z8B7PKYf3YtEzpz4iFACjfbcCW7PUxnmbD_oEcsbNqw&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&prompt=consent