I would like to use the hosted ui with amplify in react.
The authentication should be done using federated identity provider, which is working correctly.
But I don't know, how to write the react part.
I found in amplify tutorial: that is should be possible with this sample:
// OAuthButton.js
import { withOAuth } from 'aws-amplify-react';
import React, { Component } from 'react';
class OAuthButton extends Component {
render() {
return (
<button onClick={this.props.OAuthSignIn}>
Sign in with provider's account
</button>
)
}
}
export default withOAuth(OAuthButton);
// App.js
import React, { Component } from 'react';
import './App.css';
import OAuthButton from './OAuthButton';
import Amplify, { Auth, Hub } from 'aws-amplify';
import awsconfig from './aws-exports'; // your Amplify configuration
// your Cognito Hosted UI configuration
const oauth = {
domain: 'your_cognito_domain',
scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code' // or 'token', note that REFRESH token will only be generated when the responseType is code
};
Amplify.configure(awsconfig);
Auth.configure({ oauth });
class App extends Component {
constructor(props) {
super(props);
this.signOut = this.signOut.bind(this);
// let the Hub module listen on Auth events
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');
// check the current user when the App component is loaded
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 (
<div className="App">
{authState === 'loading' && (<div>loading...</div>)}
{authState === 'signIn' && <OAuthButton/>}
{authState === 'signedIn' && <button onClick={this.signOut}>Sign out</button>}
</div>
);
}
}
export default App;
In this form it is not working.
QA:
how it should looks like the OAuthSignIn method?
Because when I used the window.open method to redirect to the hosted ui url, my react app doesn't know that the user was authenticated, how the code should looks like, that the app could recognize that the login flow was finished successfully?
Related
If the keycloak user used by my react application is already signed in via another application then it shouldn't ask for authentication again via my react application (since SSO).how can i achieve this ??please provide solution to it.
i am using keycloak to authenticate a react application.keycloak authentication is working fine.tried with single-sign-on but its not working.i want that react application to work with keycloak single-sign-on i.e when keycloak user is loginned it should not ask again for login credentials.react application should work with single-sign-on.how can i achieve this ??please provide solution to it.
below is my keycloak.json
{
"realm": "Google-Auth",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required": "external",
"resource": "googledemo",
"public-client": true,
"confidential-port": 0
}
below is secured.js
`import React, { Component } from 'react';
import UserInfo from './UserInfo';
import Logout from './Logout';
import Keycloak from 'keycloak-js';
class Secured extends Component {
constructor(props) {
super(props);
this.state = { keycloak: null, authenticated: false };
}
componentDidMount() {
const keycloak = Keycloak('/keycloak.json');
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
this.setState({ keycloak: keycloak, authenticated: authenticated })
})
}
render() {
if(this.state.keycloak) {
if(this.state.authenticated) return (
<div>
<p>
This is a Keycloak-secured component of your application. You shouldn't be able to
unless you've authenticated with Keycloak.
</p>
<UserInfo keycloak={this.state.keycloak} />
<Logout keycloak={this.state.keycloak} />
</div>
); else return (<div>Unable to authenticate!</div>)
}
return (
<div>Initializing Keycloak...</div>
);
}
}
export default Secured;
I have come across a similar scenario. I used the same keycloak-js package as well. Instead of using that in component, you can try to create a service from it
import Keycloak from "keycloak-js";
const _kc = new Keycloak('/keycloak.json');
/**
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated.
*
* #param onAuthenticatedCallback
*/
const initKeycloak = (onAuthenticatedCallback) => {
_kc.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
})
.then((authenticated) => {
if (authenticated) {
onAuthenticatedCallback();
} else {
doLogin();
}
})
};
const doLogin = _kc.login;
const doLogout = _kc.logout;
const getToken = () => _kc.token;
const isLoggedIn = () => !!_kc.token;
const updateToken = (successCallback) =>
_kc.updateToken(5)
.then(successCallback)
.catch(doLogin);
const getUsername = () => _kc.tokenParsed?.preferred_username;
const hasRole = (roles) => roles.some((role) => _kc.hasRealmRole(role));
const UserService = {
initKeycloak,
doLogin,
doLogout,
isLoggedIn,
getToken,
updateToken,
getUsername,
hasRole,
};
export default UserService;
And then use the index to initiate this keycloak service and pass the render of your application as a call back.
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import HttpService from "./services/HttpService";
import UserService from "./services/UserService";
const renderApp = () => ReactDOM.render(<App/>, document.getElementById("app"));
UserService.initKeycloak(renderApp);
HttpService.configure();
I am trying to figure out how to use Firebase.
I have a config with an auth listener:
onAuthUserListener(next, fallback) {
// onUserDataListener(next, fallback) {
return this.auth.onAuthStateChanged(authUser => {
if (!authUser) {
// user not logged in, call fallback handler
fallback();
return;
}
this.user(authUser.uid).get()
.then(snapshot => {
let snapshotData = snapshot.data();
let userData = {
...snapshotData, // snapshotData first so it doesn't override information from authUser object
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerifed,
providerData: authUser.providerData
};
setTimeout(() => next(userData), 0); // escapes this Promise's error handler
})
.catch(err => {
// TODO: Handle error?
console.error('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
setTimeout(fallback, 0); // escapes this Promise's error handler
});
});
}
// ... other methods ...
// }
I have read the documentation about creating a listener to see if there is an authUser and have got this authentication listener plugged in.
import React from 'react';
import { AuthUserContext } from '../Session/Index';
import { withFirebase } from '../Firebase/Index';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.listener = this.props.firebase.auth.onAuthStateChanged(
authUser => {
authUser
? this.setState({ authUser })
: this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
};
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
Then in the consumer component I have:
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useRouteMatch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
const { Title, Text } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;
class Dashboard extends React.Component {
state = {
collapsed: false,
loading: false,
};
onCollapse = collapsed => {
console.log(collapsed);
this.setState({ collapsed });
};
render() {
return (
<AuthUserContext.Consumer>
{ authUser => (
<div>
<Text style={{ float: 'right', color: "#fff"}}>
{/*
{
this.props.firebase.db.collection('users').doc(authUser.uid).get()
.then(doc => {
console.log( doc.data().name
)
})
}
*/}
</div>
)}
</AuthUserContext.Consumer>
);
}
}
export default withFirebase(Dashboard);
It works fine the first time the page is loaded.
However, on a page refresh, the system is slower than the code and returns null error messages that say:
TypeError: Cannot read property 'uid' of null (anonymous function)
I have seen this article which proposes solutions for Angular.
I can't find a way to implement this so that it works in react.
The article suggests:
firebase.auth().onAuthStateChanged( user =>; {
if (user) { this.userId = user.uid }
});
So, in my listener I tried putting if in front of authUser - but that doesn't seem to be an approach that works.
Any advice on what to try next to make a listener that lets firebase load the user before it runs the check?
Try react-with-firebase-auth this library.
This library makes a withFirebaseAuth() function available to you.
import * as React from 'react';
import * as firebase from 'firebase/app';
import 'firebase/auth';
import withFirebaseAuth, { WrappedComponentProps } from 'react-with-firebase-auth';
import firebaseConfig from './firebaseConfig';
const firebaseApp = firebase.initializeApp(firebaseConfig);
const App = ({
/** These props are provided by withFirebaseAuth HOC */
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithGoogle,
signInWithFacebook,
signInWithGithub,
signInWithTwitter,
signInAnonymously,
signOut,
setError,
user,
error,
loading,
}: WrappedComponentProps) => (
<React.Fragment>
{
user
? <h1>Hello, {user.displayName}</h1>
: <h1>Log in</h1>
}
{
user
? <button onClick={signOut}>Sign out</button>
: <button onClick={signInWithGoogle}>Sign in with Google</button>
}
{
loading && <h2>Loading..</h2>
}
</React.Fragment>
);
const firebaseAppAuth = firebaseApp.auth();
/** See the signature above to find out the available providers */
const providers = {
googleProvider: new firebase.auth.GoogleAuthProvider(),
};
/** providers can be customised as per the Firebase documentation on auth providers **/
providers.googleProvider.setCustomParameters({hd:"mycompany.com"});
/** Wrap it */
export default withFirebaseAuth({
providers,
firebaseAppAuth,
})(App);
Technologies: I'm using Firebase Auth with NextJS & React.
Problematic: Logged users can use the web app with firebase auth normally only if they navigate within the app via Next routing, whenever they refresh the page or open a new account tab they are not logged in anymore.
Issue: It is extremely frustrating because this problem only occurs on production. There's no problem at all on the staging & localhost environment.
firebase.js: Initialize firebase.
import getConfig from "next/config";
import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/analytics';
const { publicRuntimeConfig } = getConfig();
export async function initializeFirebase() {
if (!firebase.apps.length) {
firebase.initializeApp(JSON.parse(publicRuntimeConfig.FIREBASE_CONFIG));
if (publicRuntimeConfig.FIREBASE_ANALYTICS) {
firebase.analytics();
}
}
}
export const auth = firebase.auth
export const db = firebase.firestore;
export default firebase;
AuthHoC.js: To make sure the user is connected I wrapper my pages with a HOC.
export default App => (
class AuthHoC extends App {
_isMounted = false;
constructor(props) {
super(props)
this.state = {
loading: false,
isVerified: false,
idToken: undefined,
isAuthenticated: false
}
}
async componentDidMount() {
this._isMounted = true;
await initializeFirebase();
// onAuthStateChanged return a function that we'll use to unsubscribe our listener
this.unsubscribeMethod = await auth().onAuthStateChanged(this._handleStateChange);
}
// is user is null, we're no longer authenticated
_handleStateChange = (user) => {
let that = this;
if (user) {
// NOT PASSING HERE ON PAGE REFRESH...
user.getIdToken().then(function(idToken) {
that.setState({
loading: true,
idToken: idToken,
isVerified: user.emailVerified,
isAuthenticated: !!user
});
});
} else {
...
}
}
componentWillUnmount() {
if (this.unsubscribeMethod) {
this.unsubscribeMethod();
}
this._isMounted = false;
}
render() {
return ( <>
{this.state.loading ?
<App {...this.props} {...this.state} />
:
... loading ...
}
</> )
}
});
_app.js: (NextJS) Wrap every pages with the Higher Order Component.
import App from "next/app";
import AuthHoC from '../utils/authentication/authHoC';
class MyApp extends App {
render() {
const { Component, pageProps, isAuthenticated, idToken, isVerified } = this.props;
return (
<Component
{...pageProps}
isAuth={isAuthenticated}
idToken={idToken}
isVerified={isVerified}
/>
);
}
}
export default AuthHoC(MyApp);
What could be the issue? All these codes work on localhost & staging url, just not on production.
EDIT:
I pinpointed the problem, I just switched my production keys with staging and it works, that means that the problem is not coming from the Heroku or my code but my Firebase configuration itself.
The main difference is that the prod use analytics. If you have any info I forgot to configure any suggestion would help.
I have an 'Auth' class with a 'signIn()' function that I'm trying to call from a 'Login' component, that returns the error 'not a function' when it attempts to use the callback in 'Login'.
I've tried referring to the Auth class/service via use of a prop (like this.props.auth.signIn()), and directly as via a call to the class in the Login component (like this.auth), but both result in the same error.
I'm trying to follow the method used in the official documentation for Auth0's react samples at https://github.com/auth0-samples/auth0-react-samples/blob/embedded-login/02-Custom-Login-Form/src/Login/Login.js.
Auth.js:
import auth0 from 'auth0-js';
import { auth_config } from './auth0-config';
import createHistory from 'history';
export default class Auth {
auth0 = new auth0.WebAuth({
...
})
constructor() {
this.signIn = this.signIn.bind(this);
}
signIn(username, password) {
this.auth0.login(
{
realm: auth_config.dbConnectionName,
username,
password
},
(err, authResult) => {
if (err) {
console.error('Authentication error at \'services/auth/Auth.js\' - signIn() method:', err);
alert(`Error: ${err.description}. Check the console for further details.`);
}
}
)
}
Login.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Auth from 'services/auth/Auth.js';
import { Dimmer, Modal, Header, Form, Button } from 'semantic-ui-react';
export default class LoginComponent extends React.Component {
constructor() {
super();
this.state = {
showModal: true,
username: null,
password: null,
authorised: false
}
}
componentDidMount() {
console.log('Login.props:', this.props);
}
hide = () => {
console.log('LoginComponent.hide()');
this.setState({
showModal: false
})
}
getLoginCredentials() {
return {
email: ReactDOM.findDOMNode(this.refs.email).value,
password: ReactDOM.findDOMNode(this.refs.password).value
}
}
signIn() {
// console.log('LoginComponent.signIn(\'' + this.state.username + '\', \'' + this.state.password + '\')');
const user = this.getLoginCredentials();
this.props.auth.signIn(user.email, user.password);
}
You will have to create the object of the class and then using that object you can call the function inside your Login.js,
signIn() {
const user = this.getLoginCredentials();
let signinObj = new Auth();
signinObj.signIn(user.email, user.password);
}
Edit: In the github page, in router.js file you can see the statment,
<Route path="/login" render={(props) => <Login auth={auth} {...props} />} />
auth is the object of this class which is passed to Login class as props.
Thank you reading my first question.
I trying to auth With Shared Root use react, react-router and firebase.
So, I want to keep App.js 's user state. but when I tried to refresh the browser, user state was not found.
I've tried to save to localstorage. But is there a way to keep state on component after browser refresh without localStorage?
App.js
import React, { Component, PropTypes } from 'react'
import Rebase from 're-base'
import auth from './config/auth'
const base = Rebase.createClass('https://myapp.firebaseio.com')
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
loggedIn: auth.loggedIn(),
user: {}
}
}
_updateAuth (loggedIn, user) {
this.setState({
loggedIn: !!loggedIn,
user: user
})
}
componentWillMount () {
auth.onChange = this._updateAuth.bind(this)
auth.login() // save localStorage
}
render () {
return (
<div>
{ this.props.children &&
React.cloneElement(this.props.children, {
user: this.state.user
})
}
</div>
)
}
}
App.propTypes = {
children: PropTypes.any
}
auth.js
import Rebase from 're-base'
const base = Rebase.createClass('https://myapp.firebaseio.com')
export default {
loggedIn () {
return !!base.getAuth()
},
login (providers, cb) {
if (Boolean(base.getAuth())) {
this.onChange(true, this.getUser())
return
}
// I think this is weird...
if (!providers) {
return
}
base.authWithOAuthPopup(providers, (err, authData) => {
if (err) {
console.log('Login Failed!', err)
} else {
console.log('Authenticated successfully with payload: ', authData)
localStorage.setItem('user', JSON.stringify({
name: base.getAuth()[providers].displayName,
icon: base.getAuth()[providers].profileImageURL
}))
this.onChange(true, this.getUser())
if (cb) { cb() }
}
})
},
logout (cb) {
base.unauth()
localStorage.clear()
this.onChange(false, null)
if (cb) { cb() }
},
onChange () {},
getUser: function () { return JSON.parse(localStorage.getItem('user')) }
}
Login.js
import React, { Component } from 'react'
import auth from './config/auth.js'
export default class Login extends Component {
constructor (props, context) {
super(props)
}
_login (authType) {
auth.login(authType, data => {
this.context.router.replace('/authenticated')
})
}
render () {
return (
<div className='login'>
<button onClick={this._login.bind(this, 'twitter')}>Login with Twitter account</button>
<button onClick={this._login.bind(this, 'facebook')}>Login with Facebook account</button>
</div>
)
}
}
Login.contextTypes = {
router: React.PropTypes.object.isRequired
}
If you reload the page through a browser refresh, your component tree and state will reset to initial state.
To restore a previous state after a page reload in browser, you have to
save state locally (localstorage/IndexedDB)
and/ or at server side to reload.
And build your page in such a way that on initialisation, a check is made for previously saved local state and/or server state, and if found, the previous state will be restored.