@thestartupfactory/react-auth
v1.0.9
Published
This library contains standard front end react componets required for auth flow. Flows supported and component included:
Downloads
393
Readme
tsf-react-auth
This library contains standard front end react componets required for auth flow. Flows supported and component included:
SignIn
Forgot Password
Confirm Email
Set Password
Usage
The library requires Auth API specified by the caller. The API is used to drive behaviour of the auth componets, as well as ensure the strong typing of the Aith models matching the application where library is used.
The Auth API contract:
signInFn: (details: SignInDetails) => Promise<ResponseState<AUTH_RESPONSE>>;
requestPasswordResetFn: (
request: EmailRequest
) => Promise<ResponseState<unknown>>;
confirmEmailFn: (
request: ConfirmEmailRequest
) => Promise<ResponseState<unknown>>;
setPasswordFn: (
request: SetPasswordRequest
) => Promise<ResponseState<AUTH_RESPONSE>>;
getAuthFn: () => Promise<ResponseState<AUTH_RESPONSE>>;
signOutFn: () => Promise<unknown>;
The generic type AUTH_RESPONSE must confirm to the signIn and getAuth API functions, is definted by the library user to match the auth model, e.g.:
export const authOutputModel = z.object({
id: z.string(),
email: z.string().min(1),
orgId: z.string(),
role: role,
status: activationStatus,
token: z.string().min(1),
});
export type AuthOutputModel = z.infer<typeof authOutputModel>;
In order to initialize the auth library, use createAuthComponents
init function, passing your own AuthAPI implementation, then export the library-provided compoents and hooks:
const authComponents = createAuthComponents({
signInFn: (details: Auth.signin.Input) =>
client.provide('post', '/auth', details),
requestPasswordResetFn: (request: Auth.emailrequest.Input) =>
client.provide('post', '/auth/passwordreset', request),
confirmEmailFn: (request: Auth.confirmemail.Input) =>
client.provide('put', '/auth/emailconfirmation', request),
setPasswordFn: (request: Auth.resetpassword.Input) =>
client.provide('put', '/auth/passwordreset', request),
getAuthFn: () => client.provide('get', '/auth', {}),
signOutFn: () => client.provide('delete', '/auth', {}),
});
export const useAuth = authComponents.useAuth;
export const AuthProvider = authComponents.AuthProvider;
export const SignIn = authComponents.SignIn;
export const PasswordResetRequest = authComponents.PasswordResetRequest;
export const ConfirmEmail = authComponents.ConfirmEmail;
export const SetPassword = authComponents.SetPassword;
Next, wrap up authenticated routes with the AuthProvider
component, to get access to useAuth
hook
{
path: '/',
element: <AuthProvider><AuthedRoot /></AuthProvider>),
errorElement: <ErrorScreen />,
children: [
{
path: '/organizations',
element: <Organizations.List />,
},
Next, set up routes for standard auth componet,s such as SignIn, ResetPassword...
const router = createBrowserRouter([
{
path: '/p',
element: <UnauthedRoot />,
errorElement: <ErrorScreen />,
children: [
{
path: '/p/signin',
element: authSettingRoute(
<SignIn />
),
errorElement: <ErrorScreen />,
},
{
path: '/p/passwordresetrequest',
element: (
<PasswordResetRequest />
),
errorElement: <ErrorScreen />,
},
{
path: '/p/confirmemail/:token',
element: <ConfirmEmail />,
errorElement: <ErrorScreen />,
},
{
path: '/p/setpassword/:token',
element: <SetPassword/>
,
errorElement: <ErrorScreen />,
},
],
},
Use useAuth
hook to get the current authenticated user in the component - e.g. to render different menus depending of user role or auth status:
const auth = useAuth();
const role =
auth?.user.status === 'success' ? auth.user.data.role : undefined;
return (
<>
<SideBar>
<img src={logo} alt="Coperceptuo" />
{role && ['PLATFORM_ADMIN', 'ORG_LEAD'].includes(role) && (
<StyledNavLink to={`dashboards/`}>Dashboards</StyledNavLink>
)}
To handle failed authorization (redirecting the user to the SignIn Screen), use AuthAwareFailedRequest
component
switch (request.status) {
case 'loading':
return <Loading />;
case 'error':
return <AuthAwareFailedRequestComponent
message={message}
unauthorized={message === 'Unauthorized'}
/>
case 'success': {
return <BarChart title={title} data={data} />;
}
default:
return exhaustivenessCheck(request);
}
Customizing components
Form style and structure
There are a number of React ElementTypes you can pass to the auth componets to override look&feel (not behaviour).
type ComponentOverrides = {
pageContainer?: React.ElementType;
formElement?: React.ElementType;
submitButton?: React.ElementType;
formRow?: React.ElementType;
textInput?: React.ElementType;
contactUsButton?: React.ReactElement;
};
For example, to change the top level container of auth components:
const container = styled.div`
background-color: yellow;
`;
<SignIn
components={{
pageContainer: container,
}}
/>
Custom extensions
Auth library has come extension points that allow you to add non-auth related behaviour to auth pagee. Currently only one extension is upported - adding a Contact Us button to each page, so that user can ask for help if they have trouble signing in:
const contactUs = (
<ContactButton darker={false} onClick={() => alert('Contact us')}>
Contact us
</ContactButton>
);
<PasswordResetRequest components={{ contactUsButton: contactUs }} />
Navigation behaviour
By default, the library will use redirect strategy to redirect user to sign in page on auth failure or logout. This requires specific auth routes to be set, as descibed in the Usage section above.
You can override this behaviour to use different strategy: rendering sign in component in place in case of auth failure. To do this, provide signinNavStrategy
config to the AuthAPI:
const authComponents = createAuthComponents({
signInFn: (details: Auth.signin.Input) =>
client.provide('post', '/auth', details),
requestPasswordResetFn: (request: Auth.emailrequest.Input) =>
client.provide('post', '/auth/passwordreset', request),
confirmEmailFn: (request: Auth.confirmemail.Input) =>
client.provide('put', '/auth/emailconfirmation', request),
setPasswordFn: (request: Auth.resetpassword.Input) =>
client.provide('put', '/auth/passwordreset', request),
getAuthFn: () => client.provide('get', '/auth', {}),
signOutFn: () => client.provide('delete', '/auth', {}),
signinNavStrategy:{
renderSigninScreenInPlace:true
}
});
In addition, make sure to provide the SignIn component to be rendered when user is not authenticated:
<AuthProvider
signInComponent={<SignIn/>}
>
{element}
</AuthProvider>