Amplify-js: How to configure cognito federation without using: amplify add auth?

Created on 7 Aug 2019  路  17Comments  路  Source: aws-amplify/amplify-js

* Which Category is your question related to? *
Authentication

* What AWS Services are you utilizing? *
Cognito

* Provide additional details e.g. code snippets *

I don't know how to configure cognito federated identities without running the amplify 'add auth' command. This command apparently gives me the choice: Default configuration with Social Provider (Federation)

But I already have a user pool that's set up with facebook and google federation.

I have this code, that I found in the docs for react, not react-native, and I changed it to work with react-native:

class App extends Component {
  state = { user: null, customState: null };

  componentDidMount() {
    Hub.listen("auth", ({ payload: { event, data } }) => {
      switch (event) {
        case "signIn":
          this.setState({ user: data });
          break;
        case "signOut":
          this.setState({ user: null });
          break;
        case "customOAuthState":
          this.setState({ customState: data });
      }
    });

    Auth.currentAuthenticatedUser()
        .then(user => this.setState({ user }))
        .catch(() => console.log("Not signed in"));
  }

  render() {
    const { user } = this.state;

    return (
        <Fragment>
          <Header />
          <Button onPress={() => Auth.federatedSignIn({provider: 'Facebook'})} title="Open Facebook" />
          <Button onPress={() => Auth.federatedSignIn({provider: 'Google'})} title="Open Google" />
          <Button onPress={() => Auth.federatedSignIn()} title="Open Hosted UI" />
          <Button onPress={() => Auth.signOut()} title="Sign Out" />
        </Fragment>
    );
  }
}

Pushing any of the buttons results in a Possible unhandled promis rejection. TypeError: Cannot read property: redirectSignIn of undefined.

Now, I don't know if this is a bug, or I need more configuration?

Reading through the docs, like here: https://aws-amplify.github.io/docs/js/authentication#using-components-from-aws-amplify-react

import { Authenticator } from 'aws-amplify-react/dist/Auth';

const federated = {
    google_client_id: '',
    facebook_app_id: '',
    amazon_client_id: ''
};

return (
    <Authenticator federated={federated}>
)

I see you have to configure the google_client_id and facebook_app_id manually. How can I do that in the code sample I gave? There's no "Authenticator" involved there. Is this even possible.

Auth pending-close-response-required

Most helpful comment

So, I'm just trying to figure this out by trial and error. But I'm going nowhere with this.

I would like to leave a bit of feedback on the docs.
The docs are __soo__ confusing. Cognito has a lot of options, and amplify can be used in a react and react native context. This is all mixed up in the documentation. So you get something like this: First all kinds of examples, that might be totally unusable for what you are trying to do. Then: If you use react-native or need custom logic: do this (provided in a react for web sample). The next example: if you have x but not y, do this. And so on. It would be a lot better if things were at least separated and have examples for the actual library the user is trying to implement.

All 17 comments

Hi @dionsnoeijen

Take a look at the Manual Setup section in the docs and this section too https://aws-amplify.github.io/docs/js/authentication#react-components, specifically the section for Full React Sample and the way Amplify is being configured for oauth.

Thanks! I will look at it after work :)

So, I'm trying to wrap my head around what's described in the docs. I feel I'm missing something essential in my understanding.

Just to be clear, I will describe what I want. I need three types of login in my application.

  • Google
  • Facebook
  • "normal" Cognito

I have setup a cognito user pool and configured Facebook and Google federation. I also set up an Identity Pool. I don't know if that's required or not.

Now, this is where I'm getting confused looking at the documentation.

  1. If this code example (https://aws-amplify.github.io/docs/js/authentication#oauth-and-hosted-ui) does not describe what I'm after, what does it describe? I am using that example now, but clicking the buttons give me an error TypeError: Cannot read property: redirectSignIn of undefined. Is this a bug or am I totally misunderstanding the example?
  2. Looking at another example:
const federated = {
    google_client_id: '',
    facebook_app_id: '',
    amazon_client_id: ''
};

return (
    <Authenticator federated={federated}>
)

That I know cannot be used with React Native but should I not configure those id's somewhere?. I see the facebook and google client Id is passed along to the AWS configuration. That makes sense to me, but in the example you refer to, the Full React Example. I don't see any of that plus the example is for web with redirect url's and so on.
Also, After implementing that example, converting it to ReactNative, clicking the button will open up safari to a page that is not found. So the example does not work or I don't know?. Also, since this is a mobile app, I'm curious what this config would be good for: redirectSignIn: 'http://localhost:3000/', redirectSignOut: 'http://localhost:3000/',

This is what I did:

import { withOAuth } from 'aws-amplify-react-native';
import React, { Component } from 'react';
import { Button } from 'react-native-elements';

class OAuthButton extends Component {
    render() {
        return (
            <Button onPress={this.props.facebookSignIn} title="Sign in with Facebook" />
        )
    }
}

export default withOAuth(OAuthButton);

And:

import React, { Component, Fragment } from 'react';
import { View } from 'react-native';
import Amplify, { Auth, Hub, API } from 'aws-amplify';
import OAuthButton from '../components/OAuthButton';
import {Text, Button} from 'react-native-elements';


export default class HomeScreen extends Component {
    constructor(props) {
        super(props);
        this.signOut = this.signOut.bind(this);
        Hub.listen('auth', (data) => {
            switch (data.payload.event) {
                case 'signIn':
                    this.setState({authState: 'signedIn', authData: data.payload.data});
                    break;
                case 'signIn_failure':
                    this.setState({authState: 'signIn', authData: null, authError: data.payload.data});
                    break;
                default:
                    break;
            }
        });
        this.state = {
            authState: 'loading',
            authData: null,
            authError: null
        }
    }

    componentDidMount() {
        console.log('on component mount');
        Auth.currentAuthenticatedUser().then(user => {
            console.log(user);
            this.setState({authState: 'signedIn'});
        }).catch(e => {
            console.log(e);
            this.setState({authState: 'signIn'});
        });
    }

    signOut() {
        Auth.signOut().then(() => {
            this.setState({authState: 'signIn'});
        }).catch(e => {
            console.log(e);
        });
    }

    render() {
        const { authState } = this.state;
        return (
            <View>
                {authState === 'loading' && (<Fragment><Text>loading...</Text></Fragment>)}
                {authState === 'signIn' && <OAuthButton/>}
                {authState === 'signedIn' && <Button onPress={this.signOut} title="Sign out" />}
            </View>
        );
    }
}

The configuration is done in App.js.

const oauth = {
    domain: 'https://xxxxxx.auth.eu-central-1.amazoncognito.com',
    scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
    redirectSignIn: 'http://localhost:3000/',
    redirectSignOut: 'http://localhost:3000/',
    responseType: 'code'
};

Auth.configure({ oauth });

I'm sorry if I'm missing something obvious here. I'm just not seeing it.

So, I'm just trying to figure this out by trial and error. But I'm going nowhere with this.

I would like to leave a bit of feedback on the docs.
The docs are __soo__ confusing. Cognito has a lot of options, and amplify can be used in a react and react native context. This is all mixed up in the documentation. So you get something like this: First all kinds of examples, that might be totally unusable for what you are trying to do. Then: If you use react-native or need custom logic: do this (provided in a react for web sample). The next example: if you have x but not y, do this. And so on. It would be a lot better if things were at least separated and have examples for the actual library the user is trying to implement.

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.

Yes! I'm in the same situation. I have a federated Cognito login with SAML already working with other applications, but now need to get it to work w/o the setup that amplify add auth requires. More documentation and example code are needed.

I had success by passing google_client_id as a prop to a component wrapped by the withGoogle HOC.

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.

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

The config can be passed into the Authenticator react component:
<Authenticator federated={federated} amplifyConfig={authConf}> <App /> </Authenticator>

@dbly @stemwinder could you please provide full example of your solution?

Hey everyone,

The federated config object needs to look like:

const federated = { googleClientId: "xxxxxx", facebookAppId: "xxxxxx" }

Was really hoping that I found a full working example using Authenticator and federated login.

If someone has a fully working solution a gist would be very helpful. I was loving Amplify but I'm on the _third_ page of google results right now looking for answers...

_Edit, I provided my full aws-exports config. I had to add a few more config options to get it fully working with Facebook._

Before using Amplify, I had Cognito setup with Federated Sign in with Facebook. I started with the Cognito Hosted UI, and everything worked.

I wanted to customize my own auth forms, but the Cognito docs weren't too clear to me how to manually connect my front-end with Cognito's API. Some docs suggested I move to Amplify to simplify things (including managing tokens), so I did.

Using Amplify's CLI, I started with amplify add auth, and I just selected the default configuration (no Federated authentication).

Then, in my index.tsx (same as App.js), I have:

import CssBaseline from "@material-ui/core/CssBaseline";
import Amplify from "aws-amplify";
import React from "react";

import awsAuthConfig from "./aws-exports-auth-config";
import HubListener from "./lib/hub";
import Routing from "./lib/Routing";

Amplify.configure(awsAuthConfig);

const App = () => {

  return (
    <>
      <HubListener />
      <CssBaseline />
      <Routing />
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("main"));

export default App;

I made a separate config file: awsAuthConfig.ts to replace the aws-exports.js file. In awsAuthConfig, I used the template provided from the documentation here.

At the bottom of this template, I initially commented out the section below, because I wasn't using a Hosted UI, and I thought it didn't have anything to do with my Federated Sign In. I would then also get the same error as @dionsnoeijen: "TypeError: Cannot read property: redirectSignIn of undefined.". So after some playing around, I uncommented this oauth section and filled in the details.

const authConfig = {
  Auth: {
    identityPoolId: [YOUR IDENTITY POOL ID],
    region: [YOUR REGION],
    userPoolId: [YOUR USER POOL ID],
    userPoolWebClientId: [YOUR COGNITO CLIENT ID],
    mandatorySignIn: true,
    oauth: {
      domain: [YOUR AUTH SUBDOMAIN],
      scope: [
        "phone",
        "email",
        "profile",
        "openid",
        "aws.cognito.signin.user.admin",
      ],
      redirectSignIn: [YOUR REDIRECT URI],
      redirectSignOut: [YOUR REDIRECT URI],
      responseType: "code"
    },
},
};

export default authConfig

My "Login with Facebook" button calls this handler:

  const handleFacebookLogin = async (e) => {
    await Auth.federatedSignIn({
      provider: CognitoHostedUIIdentityProvider.Facebook,
    });
  };

After doing this, my Federated Sign In works now (with Facebook).

Below is what I got working using:

  • Angular
  • Cognito hosted login page
  • Google G Suite as an SAML2 Identity provider (setup manually on cognito user pool after amplify add auth and amplify push)

srcaws-exports.ts

const awsmobile = {
    "aws_project_region": "ap-southeast-2",
    "aws_cognito_identity_pool_id": "ap-southeast-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "aws_cognito_region": "ap-southeast-2",
    "aws_user_pools_id": "ap-southeast-2_xxxxxxxxx",
    "aws_user_pools_web_client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
    "oauth": {
        "domain": "xxxxxxxxxxxxxxxxxxxxxxxxxx.auth.ap-southeast-2.amazoncognito.com",
        "scope": [
            "phone",
            "email",
            "openid",
            "profile",
            "aws.cognito.signin.user.admin"
        ],
        "redirectSignIn": "http://localhost:4200/",
        "redirectSignOut": "http://localhost:4200/",
        "responseType": "code"
    },
    "federationTarget": "COGNITO_USER_POOLS",
    "aws_appsync_graphqlEndpoint": "https://xxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-southeast-2.amazonaws.com/graphql",
    "aws_appsync_region": "ap-southeast-2",
    "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS"
};

export default awsmobile;

src\app\app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import Amplify from 'aws-amplify';
import awsconfig from '../aws-exports';

Amplify.configure(awsconfig);

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

src\app\app.component.ts

import { Component, OnInit } from '@angular/core';
import { Auth, Hub, Logger } from 'aws-amplify';

const log = new Logger('AppComponent', 'DEBUG');

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})

export class AppComponent implements OnInit {
  title = 'My AppComponent';

  authState = { loading: true, authenticated: false, user: null };

  async ngOnInit() {

    Hub.listen("auth", ({ payload: { event, data } }) => {
      log.debug("ngOnInit-listen-auth-event:", event);
      switch (event) {
        case "signIn":
          log.debug("ngOnInit-listen-auth-signIn-data:", data);
          this.authState = { loading: false, authenticated: true, user: data };
          break;
        case "signOut":
          log.debug("ngOnInit-listen-auth-signOut-data:", data);
          this.authState = { loading: false, authenticated: false, user: null };
          break;
      }
    });

    Auth.currentAuthenticatedUser().then(user => {
        log.debug("ngOnInit-currentAuthenticatedUser:", user);
        this.authState = { loading: false, authenticated: true, user: user };
    }).catch(e => {
        log.debug("ngOnInit-currentAuthenticatedUser:", e);
        this.authState = { loading: false, authenticated: false, user: null };
    });
  }

  getCurrentUser()
  {
    log.debug("getCurrentUser:", this.authState.user);
    return this.authState.user;
  }

  isAuthStateLoading()
  {
    log.debug("isLoading-authState.loading:", this.authState.loading);
    log.debug("isLoading-authState.user:", this.authState.user);

    if(this.authState.loading)
    {
      return true;
    }

    if(this.authState.authenticated && (this.authState.user == null || this.authState.user.attributes == null))
    {
      return true;
    }

    return false;
  }

  isSignedIn()
  {
    log.debug("isSignedIn:", this.authState.authenticated);
    return this.authState.authenticated;
  }

  signIn() {
    Auth.federatedSignIn().then(credentials => {
          log.debug("signIn-federatedSignIn", credentials);
        }).catch(e => {
          log.debug("signIn-federatedSignIn", e);
        });
  }

  signOut() {
      Auth.signOut().then(() => {
          log.debug("signOut");
          this.authState = { loading: false, authenticated: false, user: null };
      }).catch(e => {
          log.debug("signOut:", e);
      });
  }

}

src\app\app.component.html

<div *ngIf="isAuthStateLoading()">
  <p>Loading ...</p>
</div>

<div *ngIf="!isAuthStateLoading()">

  <p>My AppComponent</p>

  <div *ngIf="!isSignedIn()">
    <button (click)="signIn()">Sign In</button>
  </div>

  <div *ngIf="isSignedIn()">
    <p>Email: {{ getCurrentUser().attributes.email }}</p>
    <p>Firstname: {{ getCurrentUser().attributes.given_name }}</p>
    <p>Lastname: {{ getCurrentUser().attributes.name }}</p>
    <button (click)="signOut()">Sign Out</button>
  </div>

</div>

The config can be passed into the Authenticator react component:
<Authenticator federated={federated} amplifyConfig={authConf}> <App /> </Authenticator>

What about <AmplifyAuthenticator />? This doesn't seem to work with the new @aws-amplify/ui-react library

Update
Added this a while ago, but feel free to ask me questions about it.

// aws-exports.js
...
"federationTarget": "COGNITO_USER_POOLS",
...
// Authenticator.js (Custom wrapper class for my App)
import React, {useEffect, useState} from 'react';
import Amplify, { Auth, Hub } from 'aws-amplify';
import { AmplifySignOut } from '@aws-amplify/ui-react';
import Dashboard from "./Dashboard";
import { Button } from 'react-bootstrap';

const AuthStateApp = () => {
    const [user, setUser] = useState(null);
    const [userName, setUserName] = useState(null);

    useEffect(() => {
     Hub.listen('auth', ({ payload: { event, data } }) => {
       switch (event) {
         case 'signIn':
         case 'cognitoHostedUI':
           getUser().then(userData => setUser(userData));
           break;
         case 'signOut':
           setUser(null);
           setUserName(null);
           break;
         case 'signIn_failure':
         case 'cognitoHostedUI_failure':
           console.log('Sign in failure', data);
           break;
       }
     });

     getUser().then(userData => setUser(userData));
    }, []);

    function getUser() {
     return Auth.currentAuthenticatedUser()
       .then(userData => userData)
       .catch(() => console.log('Not signed in'));
    }

    return (
      <div>
        {user ? (
          <div>
            <Dashboard firstName={user.attributes.name}/>
            <AmplifySignOut />
          </div>
        ) : (
          <Button
            onClick={() => Auth.federatedSignIn({provider: 'Facebook'})}>Continue with Facebook</Button>
        )}
      </div>
    );
}

export default AuthStateApp;

The federation is handled by your cognito user pool. This way all the social providers are handled in one place.

@jgiunta1 it worked for me, as someone else mentioned the federated object schema is

const federated = {
  googleClientId: string,
  facebookAppId: string
};

(edit: I should say at least this is part of the schema)

Was this page helpful?
0 / 5 - 0 ratings