The default Auth UI looks good. However we just like perfection. Let’s replace it.
Start from the easiest, <SignOut>
button. Not only it has the least elements, but also it lives outside of <Authenticator>
create src/components/auth/JSignOut.jsx
import React, { Component } from 'react';
import { Button } from 'bootstrap-4-react';
import { Auth } from 'aws-amplify';
export default class JSignOut extends Component {
constructor(props) {
super(props);
this.signOut = this.signOut.bind(this);
}
signOut() {
Auth.signOut();
}
render() {
return (
<Button light outline sm border="0" onClick={this.signOut}>Sign Out</Button>
)
}
}
Update src/components/Navigator.jsx
to import and render <JSignOut>
instead.
import { JSignOut } from './auth';
...
render() {
...
{ user && <JSignOut /> }
...
}
...
Let’s create a sign in form.
Save the form to src/components/auth/JSignIn.jsx
. Implement sign in,
signIn() {
const { username, password } = this.inputs;
Auth.signIn(username, password)
.then(user => this.signInSuccess(user))
.catch(err => this.signInError(err));
}
Then modify src/pages/Login.jsx
, hide the default <SignIn>
, add our <JSignIn>
import React, { Component } from 'react';
import { Lead, BSpan } from 'bootstrap-4-react';
import { Authenticator, SignIn } from 'aws-amplify-react';
import { JSignIn } from '../components/auth';
const CustomAuthenticator = props => (
<Authenticator hide={[SignIn]}>
<JSignIn />
</Authenticator>
)
export default class Login extends Component {
render() {
const { user } = this.props;
return (
<React.Fragment>
{ !user && <CustomAuthenticator /> }
{ user && <Lead>You are signed in as <BSpan font="italic">{user.username}</BSpan>.</Lead> }
</React.Fragment>
)
}
}
When sign in successful with <JSignIn>
seems nothing actually happens. This is because by default user is required to confirm sign in via SMS. We don’t have that yet. <Authenticator>
has the confirm SMS form. Let’s see how to link <JSignIn>
with other forms.
Emit authState/authData
By putting a component inside <Authenticator>
, a few properties are injected by it. One of the properties is onStateChange
. Every component inside <Authenticator>
should notify auth state change via this handler.
Modify src/components/auth/JSignIn.jsx
,
signInSuccess(user) {
this.setState({ error: '' });
const { onStateChange } = this.props;
if (!onStateChange) { return; }
if (user.challengeName === 'SMS_MFA' || user.challengeName === 'SOFTWARE_TOKEN_MFA') {
onStateChange('confirmSignIn', user);
} else {
onStateChange('signedIn', user);
}
}
Hide JSignIn after Sign In
Notice <JSignIn>
shows along with confirmation form. We should fix it.
Every component inside <Authenticator>
got authState
property. So just check authState
in render
method. JSignIn should show up in three states: ‘signIn’, ‘signedOut’, ‘signedUp’
render() {
const { authState } = this.props;
if (!['signIn', 'signedOut', 'signedUp'].includes(authState)) { return null; }
...
withFederated
HOC turns buttons into Federated sign in button.
props.facebookSignIn | props.googleSignIn | props.amazonSignIn
at the right timefederated
property with app idsonStateChange
to notify sign in eventimport { withFederated } from 'aws-amplify-react';
const FederatedButtons = (props) => (
<Button
secondary
mt="1"
style=
onClick={props.facebookSignIn}
>
Facebook
</Button>
);
const Federated = withFederated(FederatedButtons);
const federated_data = {
google_client_id: '',
facebook_app_id: '__replace_with_your_facebook_app_id__',
amazon_client_id: ''
};
...
// in login form render method,
// trigger props.onStateChange when state changes, i.e. signed in
<Federated federated={federated_data} onStateChange={this.props.onStateChange} />
Now let’s replace sign in confirmation form.
Create src/components/JConfirmSignIn.jsx
with
Auth.confirmSignIn(user, code, mfaType)
.then(() => this.confirmSuccess(user))
.catch(err => this.confirmError(err));
Then add it to <CustomAuthenticator>
like we did for <JSignIn>
User may forget password. In order to be able to recover password, user has to have one of the contact info verified. We should prompt user about this.
The check may happen in both <JSignIn>
and <JConfirmSignIn>
. We show one example, the other is same.
Update src/components/auth/JSignIn.jsx
:
constructor(props) {
super(props);
this.checkContat = this.checkContact.bind(this);
this.signIn = this.signIn.bind(this);
}
checkContact(user) {
Auth.verifiedContact(user)
.then(data => {
if (!JS.isEmpty(data.verified)) {
this.changeState('signedIn', user);
} else {
user = Object.assign(user, data);
this.changeState('verifyContact', user);
}
});
}
signIn() {
const { username, password } = this.inputs;
logger.debug('username: ' + username);
Auth.signIn(username, password)
.then(user => this.checkContact(user))
.catch(err => this.error(err));
}
Do the same to src/components/auth/JConfirmSignIn.jsx
.
<JSignIn>
should have a way to go to sign up form. Inside <Authenticator>
this is achieved by emitting authState
‘signUp’
<Col text="left">
New to us?{' '}
<BA href="#" preventDefault onClick={() => this.changeState('signUp')}>
Sign up
</BA>
</Col>
Now on click we’ll see Sign Up form, but it is the default form. Go through the same process create a RegisterForm. Same to other UI components in auth flow.
hideDefault
In order to replace default Auth forms, we provide hide
list to Authenticator
. Once all replaced, we could simply pass a hideDefault
property, no need to write the whole list.
<Authenticator hideDefault />
npm start