Sign in to End-User Application with AxAuth IDP
Introduction
@axinom/mosaic-user-auth is a react library provided to be used by the frontend application to make end-user related authentication and other end-user related operations easier.
This article describes use cases where the library methods can be used in relation with AxAuth IDP.
Sign Up
Apart from the signing in through an OAuth 2.0 compatible IDP, the Mosaic User Service supports standalone user management. Using this functionality, frontend application developers can introduce user sign up functionality and signing in through those newly created user accounts using username/passsword authentication flow.
Below is how user sign up can be configured using the helper methods
provided by @axinom/mosaic-user-auth library. To utilize the user
sign up process, the Administrator must configure an AxAuth IDP
in the User Service. And the corresponding AxAuth user store must
have a valid User Sign Up Webhook
configured. (Figure 2)
AxAuth User Store configuration
When the initiateUserSignUp
is called, the User Sign Up Webhook
configured in the AxAuth user store is called passing the User
Sign Up OTP. The developer must handle this and pass this OTP to
the end-user (i.e. via email).
The developer has the choice here to provide a link/a page to the user,
where the user would click on/provide the OTP, and eventually call
the completeUserSignUp
method in the @axinom/mosaic-user-auth library,
or to verify the user straightaway after initiating the sign up process.
The sign in process will only work for verified users, and the user service library gives the freedom of choice for the developer to decide how the sign up process is handled.
const SignUp: React.FC = () => {
const { initiateUserSignUp } = useUserService();
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [error, setError] = useState<string>('');
return (
<Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" textAlign="center">
Sign-up for a new account
</Header>
<Form size="large" onSubmit={async (event) => {
event.preventDefault();
// Make the first request to the User Service, initiating the User Sign Up flow.
const signUpResponse = await initiateUserSignUp(`${END_USER_APP_BASE_URL}/signup`, {email, password});
if (signUpResponse.code === UserSignUpResponseCode.SUCCESS) {
window.location.assign('/login');
} else {
setError(signUpResponse.message ? signUpResponse.message : 'Error Signing up.');
}
}}>
<Segment>
<Message color="red"hidden={error === ''}>{error}</Message>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email address"
type="email"
onChange={(event) => setEmail(event.target.value)}
value={email}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
type="password"
onChange={(event) => setPassword(event.target.value)}
value={password}
/>
<Button color="black" fluid size="large">
Register
</Button>
</Segment>
</Form>
</Grid.Column>
</Grid>
)
};
Sign Up User
Signing In
For users that are signing up using AxAuth IDP, the @axinom/mosaic-user-auth
library provides the capability of signing in using the username/password flow. The developers
can make use of signIn
method for this purpose. Below is a sample
code where a react component is making use of this method.
import { useUserService } from '@axinom/mosaic-user-auth';
export const Login: React.FC = () => {
const [email, setEmail] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [error, setError] = useState<string>('');
const handleSignIn = async(event: React.FormEvent<HTMLFormElement>): Promise<void> => {
event.preventDefault();
// call the signIn method with the email and password
const signInResponse = await signIn({
email: email,
password: password
});
if (signInResponse.code === SignInResponseCode.SUCCESS) {
window.location.assign('/');
} else {
setError(`Unable to Sign In. ${signInResponse.details?.error ?? signInResponse.message}`)
}
}
return (
<Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
<Grid.Column style={{ maxWidth: 450 }}>
<Form size="large" onSubmit={async (event: React.FormEvent<HTMLFormElement>, data: FormProps) => {
await handleSignIn(event);
}}>
<Segment>
<Header as="h5" textAlign="center">
StreamApp Account
</Header>
<Message color="red" hidden={error === ''}>{error}</Message>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email address"
id="email"
value={email}
onChange={(event) => {setEmail(event.target.value)}}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
type="password"
id="password"
value={password}
onChange={(event) => {setPassword(event.target.value)}}
/>
<Button type = "submit" primary fluid size="large">
Sign In
</Button>
</Segment>
</Form>
</Grid.Column>
</Grid>
);
};
Sign In
Handling the Access Token
After the sign in process is completed, the access token for the
user can be retrieved by calling the getToken
method. This can
be done in the Root component as below.
export const App: React.FC = () => {
const { getToken, addTokenChangedHandler, removeTokenChangedHandler } =
useUserService();
const [tokenResponse, setTokenResponse] = useState<TokenResponse | null>(
null,
);
useEffect(() => {
// Get the access token and store in tokenResponse.
// If the token changes or removed, call getToken()
// and refresh the token.
(async () => {
setTokenResponse(await getToken());
})();
}, [addTokenChangedHandler, getToken, removeTokenChangedHandler]);
return (
<BrowserRouter>
<Switch>
<Route path="/profiles">
<ProfileSelector />
</Route>
<Route>
{tokenResponse === null ? (
<LoadingPlaceholder />
) : tokenResponse.userToken === undefined ? (
<LandingPage />
) : (
<ProfileSelector />
)}
</Route>
</Switch>
</BrowserRouter>
);
}
In the above code, the router checks if there’s a valid access token
in the tokenResponse
variable. If there is an access token, which
means the user has successfully signed in, it displays the
ProfileSelector
component. Or else, it displays a generic
LandingPage
component.
Handling Password Resets
AxAuth IDP provides Forgot Password functionality for users that want
to reset their password. This is a two step process where first the
user initiates a request showing the intent of wanting to change
their password (initiateResetPassword
method). Next, a Password Reset OTP will be generated by AxAuth
that must be subsequently communicated to the user by the developer.
This Password Reset OTP will be forwarded to the Forgot Password Webhook
defined in AxAuth User Store once its generated. The developer must
take care of communicating this to the end-user via email or any other
means.
Then the password reset flow must be completed by the user entering
the Password Reset OTP (completeResetPassword
method). The new password must be either provided
with the initiate request or the complete request. Failing to do
so will result in an error.
Initiating the Password Reset flow
const ResetPassword: React.FC = () => {
const { initiateResetPassword } = useUserService();
const [email, setEmail] = useState<string>('');
const [error, setError] = useState<string>('');
return (
<Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" textAlign="center">
Reset Password
</Header>
<Form size="large" onSubmit={async (event) => {
event.preventDefault();
// call initiateResetPassword method
const signUpResponse = await initiateResetPassword(`${END_USER_APP_BASE_URL}/reset-password`, email);
if (signUpResponse.code === PasswordResponseCode.SUCCESS) {
window.location.assign('/complete-reset-password');
} else {
setError(signUpResponse.message ?? 'Error resetting password.');
}
}}>
<Segment>
<Message>An OTP code will be sent to the following email address to reset the password.</Message>
<Message color="red"hidden={error === ''}>{error}</Message>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email address"
type="email"
onChange={(event) => setEmail(event.target.value)}
value={email}
/>
<Button color="black" fluid size="large">
Confirm
</Button>
</Segment>
</Form>
</Grid.Column>
</Grid>
)
};
Initiate Reset Password Flow
Completing the Password Reset flow
const CompleteResetPassword: React.FC = () => {
const { completeResetPassword } = useUserService();
const [password, setPassword] = useState<string>('');
const [otp, setOtp] = useState<string>('');
const [error, setError] = useState<string>('');
return (
<Grid textAlign="center" style={{ height: '100vh' }} verticalAlign="middle">
<Grid.Column style={{ maxWidth: 450 }}>
<Header as="h2" textAlign="center">
Confirm New Password
</Header>
<Form size="large" onSubmit={async (event) => {
event.preventDefault();
// call the completeResetPassword with the Reset OTP
const signUpResponse = await completeResetPassword({newPassword: password, resetOtp: otp});
if (signUpResponse.code === PasswordResponseCode.SUCCESS) {
window.location.assign('/login');
} else {
setError(signUpResponse.message ?? 'Error resetting password.');
}
}}>
<Segment>
<Message hidden={error !== ''}>Enter the new password along with the OTP code sent to your registered email.</Message>
<Message color="red"hidden={error === ''}>{error}</Message>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="New password"
type="password"
onChange={(event) => setPassword(event.target.value)}
value={password}
/>
<Form.Input
fluid
icon="key"
iconPosition="left"
placeholder="OTP"
onChange={(event) => setOtp(event.target.value)}
value={otp}
/>
<Button color="black" fluid size="large">
Reset Password
</Button>
</Segment>
</Form>
</Grid.Column>
</Grid>
)
};
Complete Reset Password Flow