aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

.JWT return from Cache.getItem('federatedInfo') isn't a proper token. #1011

Closed michaelcuneo closed 5 years ago

michaelcuneo commented 6 years ago

When I log people in, I'm using session.idToken.jwtToken to get a token for the Cognito user information, which works fine...

When I log federated users in, I'm using the result from Cache.getItem, to get a token for the Federated Info...

Google will return me a valid .JWT token full of user information, name, picture, email, etc.

Facebook returns me some kind of Token that isn't a valid .JWT

""

This works to authenticate me against all the things that I need to be accessing after I've logged in... but it has no information that I can extract using normal jwtDecode methods. I've been trying to get this to work for days. There really shouldn't need to be three different ways to get the information of a user.

powerful23 commented 6 years ago

@michaelcuneo sorry about that but for now there is some confusing part about the federate user sign in. We are working to provide a uniformed way to retrieve the jwt token. Can you tell me which way you are signing the federated users in? Using Cognito hosted UI or Auth.federatedSignIn? Or any component from aws-amplify-react?

powerful23 commented 6 years ago

Related to #980

michaelcuneo commented 6 years ago

Slightly different to #980 It is indeed the same purpose, but using different methods. I'm using the aws-amplify-react's Authenticator HOC.

powerful23 commented 6 years ago

@michaelcuneo can you paste some code snippet about how you are using the Authenticator HOC?

michaelcuneo commented 6 years ago

The Authenticator HOC is inside it's own component, and it's being shown or not based on loggedIn() ? results in my react-router switch. All of this is irrelevant as to why aws amplify is storing and returning an 'authToken instead of a .JWT' from the cache, but here's my code anyway.

AuthLogin container. https://pastebin.com/Q8vikADi

App Container. https://pastebin.com/UGQ6UcWi

Inside the SAGA that runs all my actions after login. I've tried multiple ways to get the .JWT of a Facebook user. But it seems that Cognito only stores some access token or something instead.

old method of getting the .JWT token.

    // Get the current credentials if they exist.
    const userInfo = yield call(getCurrentCredentials);

    /* .JWT is not returned so try a different method to get a .JWT */
    if (userInfo.params.Logins['graph.facebook.com']) {
      token = userInfo.params.Logins['graph.facebook.com'];
      decoded = decode(token);
      user = {
        userName: `${decoded.given_name} ${decoded.family_name}`,
        picture: decoded.picture,
      };
    }

new method of getting the .JWT token.

token = GetToken();
.
.
.
async function GetToken() {
  return Cache.getItem('federatedInfo');
}

It seems that it doesn't matter what method I used to get a .JWT token for Facebook logins, because it just doesn't store a .JWT.

My old method using the result from userInfo.params.Logins['graph.facebook.com'] also gave me an access token, not a .JWT.

michaelcuneo commented 6 years ago

I can get a .JWT from my Cognito User Pool users, and my Google users, which can be used to grab their name, email, photos, etc... but not Facebook. The code doesn't appear to be the problem, it's the cache, or storage, storing something that isn't a .JWT for Facebook logins. I don't know what it is though... I've tried modifying the project end on Facebook, but still no .JWT's for FB.

If I was able to map the username/email/picture etc to cognito user pools on signin, that would be great, I could just grab that info out of there instead... but without the .JWT token there's nothing to grab from anywhere.

powerful23 commented 6 years ago

First I want to make it clear is that there are two different ways for users to "sign in" with federated users:

  1. via Cognito Hosted UI: By this way you are actually mapping those federated users into your Congito Pool as external users and you can use Auth.currentSession() to get the current session and then get the JWT token like what you do with Cognito users.
  2. via Federated identities: By this way you are actually using the JWT token from Facebook or Google to get you an AWS Credentials from Cognito Federated Identity Pool which is another service from Cognito and has nothing to do with the user pool. So the JWT token cannot be fetched from session because in this case you have no session but only a token from Facebook or Google. Amplify will store that token into Cache so you can achieve that.

You are using the second the way to log user in and it is correct to get the JWT token by Cache.getItem('federatedInfo'). Since we didn't do anything with that JWT token from Facebook except putting it into the cache so for now I have no clue about this issue. But I can try to reproduce it and maybe find a way to solve. Thanks.

michaelcuneo commented 6 years ago

Just to test this out properly, I've reproduced it in an App that I set up from scratch just now.

Here are my methods to reproduce.

npx create-react-app test-app cd test-app npm install aws-amplify npm install aws-amplify-react npm run start

opened the project and replaced the App.js with the following code. (Important info replaced with xxxxx's for this post)

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

import Amplify, { Cache, Hub } from 'aws-amplify';
import { Authenticator } from 'aws-amplify-react';
import awsExports from './aws-exports';

Amplify.configure(awsExports);

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      provider: null,
      token: null,
    };

    Hub.listen('auth', this, 'LoginListener');
  }

  onHubCapsule(capsule) {
    const { channel, payload } = capsule;
    if (channel === 'auth') { this.onAuthEvent(payload); }
  }

  async onAuthEvent(payload) {
    const { event, data } = payload;
    switch (event) {
      case 'signOut':
        Cache.clear();
        this.setState({
          provider: null,
          token: null,
        })
        break;
      case 'signIn':
        const federatedInfo = await this.getFederatedInfo();
        if (federatedInfo) {
          this.setState({
            provider: federatedInfo.provider,
            token: federatedInfo.token,
          });
        }
        break;
      default:
    }
  }

  async getFederatedInfo() {
    return await Cache.getItem('federatedInfo');
  }

  render() {
    let token = this.state.token;
    let provider = this.state.provider;
    const federated = {
      google_client_id: 'xxxxxxxx.apps.googleusercontent.com',
      facebook_app_id: 'xxxxxxxx',
    };
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <Authenticator federated={federated} />
        { provider? <p>{provider}</p> : <p> NO PROVIDER YET</p> }
        { token? <p>{token}</p> : <p>NO TOKEN YET</p> }
      </div>
    );
  }
}

export default App;

The aws-exports.js is set up with typical Facebook / Google / Cognito information from an aws-mobile setup. (Important info replaced with xxxxx's for this post)

const awsmobile = {
    'aws_app_analytics': 'enable',
    'aws_auth_facebook': 'enable',
    'aws_cloud_logic': 'enable',
    'aws_cloud_logic_custom': [{"id":"xxxxx","name":"imageSetsCRUD","description":"","endpoint":"https://xxxxx.execute-api.ap-southeast-2.amazonaws.com/Development","region":"ap-southeast-2","paths":["/imageSets","/imageSets/123"]},{"id":"xxxxx","name":"userSettingsCRUD","description":"","endpoint":"https://xxxxx.execute-api.ap-southeast-2.amazonaws.com/Development","region":"ap-southeast-2","paths":["/userSettings","/userSettings/123"]}],
    'aws_cognito_identity_pool_id': 'ap-southeast-xxxxx',
    'aws_cognito_region': 'ap-southeast-2',
    'aws_content_delivery': 'enable',
    'aws_content_delivery_bucket': 'koalacall-hosting-mobilehub-xxxxx',
    'aws_content_delivery_bucket_region': 'ap-southeast-2',
    'aws_content_delivery_cloudfront': 'enable',
    'aws_content_delivery_cloudfront_domain': 'xxxxx.cloudfront.net',
    'aws_dynamodb': 'enable',
    'aws_dynamodb_all_tables_region': 'ap-southeast-2',
    'aws_dynamodb_table_schemas': [{"tableName":"xxxxx","attributes":[{"name":"userId","type":"S"},{"name":"currentImageSet","type":"S"},{"name":"exclusionSet","type":"NS"},{"name":"shownSettings","type":"BOOL"}],"indexes":[],"region":"ap-southeast-2","hashKey":"userId"},{"tableName":"koalacall-mobilehub-887331565-imageSets","attributes":[{"name":"imageSetId","type":"S"},{"name":"status","type":"S"},{"name":"imageCount","type":"S"},{"name":"sa2Id","type":"N"}],"indexes":[{"indexName":"status","hashKey":"status"}],"region":"ap-southeast-2","hashKey":"imageSetId","rangeKey":"status"}],
    'aws_facebook_app_id': 'xxxxx',
    'aws_facebook_app_permissions': 'public_profile',
    'aws_google_app_permissions': 'email,profile,openid',
    'aws_mandatory_sign_in': 'enable',
    'aws_mobile_analytics_app_id': 'xxxxx',
    'aws_mobile_analytics_app_region': 'us-east-1',
    'aws_project_id': 'xxxxx',
    'aws_project_name': 'Koala Call',
    'aws_project_region': 'ap-southeast-2',
    'aws_resource_bucket_name': 'xxxxx',
    'aws_resource_name_prefix': 'xxxxx',
    'aws_sign_in_enabled': 'enable',
    'aws_user_files': 'enable',
    'aws_user_files_s3_bucket': 'xxxxx',
    'aws_user_files_s3_bucket_region': 'ap-southeast-2',
    'aws_user_pools': 'enable',
    'aws_user_pools_id': 'xxxxx',
    'aws_user_pools_web_client_id': 'xxxxx',
}

export default awsmobile;

And here are my results after successfully logging in with Cognito / Facebook & Google.

cognito facebook google

As you can see... the Facebook result is not a valid .JWT.

powerful23 commented 6 years ago

@michaelcuneo yeah you are right. Facebook returns an access token rather then a .JWT token. Seems like you need to fb.api('/me', response) to get those user attributes.

michaelcuneo commented 6 years ago

Is that even possible? fb doesn't exist in my app... I'm using Authenticator. That's the whole point of using this HoC... to have it automatically do all of the stuff that I would normally have done manually.

So if there's no Cache < -- > response or Auth. < -- > response that could ever possibly give me this user information, then I can't get this information... Can't the HoC store the .JWT instead of that Auth Token? The Auth Token is great and all, it's necessary in the system, but it should be consistent. Store the .JWT in the Cache federatedInfo, then use that to reference to the access token somewhere lower down in the actual HoC component... I'm still tracing it backward to try and find a fix for this... but this is the way it should work, yes?

powerful23 commented 6 years ago

@michaelcuneo you can get the facebook object by using window.fb. The HOC is just a wrapper around the facebook login and Facebook only provides a token which is not .JWT. You are right that we should provide a uniformed way to get user info.

vonkanehoffen commented 6 years ago

I'm still pretty confused by all this too. @powerful23 when using the second way you mentioned above (Federated Identities and Cache.getItem('federatedInfo') ) how are you meant to use that token to authenticate requests? The token I get from a non-federated flow via Auth.currentSession().idToken.jwtToken I can just send in an authorization header to the back-end and it works fine, but when I try sending the federated token in it's place, I'm not authorised.

Docs here don't really make it clear... unless I'm just being thick! ;-)

martimarkov commented 6 years ago

@vonkanehoffen the federatedInfo token is the access token from Facebook for example. You can use that to query FB for user info directly.

Jaikant commented 5 years ago

I am using AppSync and need to send the JWT token in the header. But there isn't any JWT! Does that mean AppSync with Cognito Pools based auth, will not work with FB and Google. And I would have to change it to IAM based auth in AppSync? Thats isn't pretty.

Below is what I am trying to do: get the access token:
const accessToken = (await Auth.currentSession()).getIdToken().getJwtToken();

Set it in the header:

  "x-api-key" : `${process.env.SF_AWS_APPSYNC_API_KEY}`,
    authorization : `${accessToken}`
Jaikant commented 5 years ago

So I managed to work around this issue by following the Cognito Hosted UI in Amplify as described here.

Also this reddit post helped, in which it is clearly stated that we need to use the hosted ui method to add federated users to cognito user pools. (without using identity pools).

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 5 years ago

This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.