@lavieennoir/auth
v1.3.6
Published
Auth boilerplate for frontend JS projects
Downloads
7
Maintainers
Readme
@lavieennoir/auth
JWT authentication is easy as never before ☕
Visit the lavieennoir.github.io/auth for tutorials and documentation!
Features
- 🔑 Handle user sign in process.
- 🗄 Persist user access tokens in
localStorage
. - 💫 Send authorized API request with no additional configuration.
- 🤖 Handle refresh process for expired access tokens
- 😻 Provide user data through the Context in React apps.
- 🪶 Super lightweight
Talk is cheap. Show me the code!
Installation 🔄
npm install @lavieennoir/auth
# or using yarn
yarn add @lavieennoir/auth
Table of Contents
Usage guides 👀
Default ReactJS use case
🧑💻 If you don't use React or just want to have more fine-grained control over the library, see Usage with TypeScript section.
- Define interfaces that are used to communicate with auth API
IUser
interface represents user object that is returned from the getUser
function once user is authorized. Also this interface represents user
property returned from useAuthContext
hook.
ISignInParams
interface represents parameters consumed by sign-in endpoint. Usually it is email and password or username and password.
You will need to pass an object with params determined by this interface when performing user sign-in (authManager.signIn(signInParams)
call)
Both name and definition of these interfaces should be configured according to the business logic of your application.
// utils/auth-options.types.ts
export interface IUser {
id: string;
email: string;
username: string;
role: string;
}
export interface ISignInParams {
email: string;
password: string;
}
- Define configuration that will be used by AuthProviders
Interfaces declared in above are used to type options object that will be used to initialize auth manager (and in some other places of your app). The definition of all options is described in the IAuthOptions section of the API reference.
// utils/auth-options.ts
import type { IAuthOptions } from '@lavieennoir/auth';
import type { IUser, ISignInParams } from './auth-options.types';
// Define options that will be passed to AuthProvider
export const authOptions: IAuthOptions<IUser, ISignInParams> = {
axiosInstance: axios.create({
baseURL: 'https://my-app.com/api',
headers: {
'Content-Type': 'application/json',
},
}),
// send email and password to POST https://my-app.com/api/auth/sign-in
signIn: (signInParams, manager) => manager.axios.post('/auth/sign-in', signInParams),
// send refresh token to POST https://my-app.com/api/auth/refresh'
refreshToken: (manager) =>
manager.axios.post('/auth/refresh', { refreshToken: manager.getRefreshToken() }),
// get user object from GET https://my-app.com/api/profile'
getUser: (manager) => manager.axios.get('/profile'),
};
- Wrap you app with an
AuthProvider
AuthProvider
is based on React Context feature. So you will not be able to access authorization data & functions in components that are rendered above the AuthProvider
.
If config
property of AuthProvider
is changed it will reinitialize authorization manager, so it is important to keep authOptions
constant so as not to harm the performance of the application.
// App.tsx
import type { authOptions } from './auth-options';
const App = () => {
return (
<AuthProvider config={authOptions}>
<SignInButton />
<User />
</AuthProvider>
);
};
- Define typed version of library functions (optional, but recommended)
While it's possible to import the IUser
and ISignInParams
types (declared above) into each component, it's better to create typed versions of the useAuthContext
and getAuthManager
functions for usage in your application.
Since these are actual variables, not types, it's important to define them in a separate file such as utils/auth.ts
. This allows you to import them into any component file that needs to use these functions, and avoids potential circular import dependency issues.
// utils/auth.ts
import { getAuthManager } from '@lavieennoir/auth';
import { useAuthContext } from '@lavieennoir/auth/react';
import type { IUser, ISignInParams } from 'utils/auth-options.types';
// Use throughout your app instead of plain `useAuthContext` and `getAuthManager`
export const useAppAuthContext = () => useAuthContext<IUser>();
export const getAppAuthManager = () => getAuthManager<IUser, ISignInParams>();
- Use
useAppAuthContext
to access authorization data
After the application mount, auth library takes some time to initialize so you need to handle that case in your component using isLoading
field returned from the hook. Once isLoading
is set to true
you can access authorization data.
// User.tsx
import { useAppAuthContext } from 'utils/auth';
const User = () => {
const { isLoading, isSignedIn, user } = useAppAuthContext();
if (isLoading) return <Loader />;
return <p>{isSignedIn ? `Email: ${user.email}` : 'Unauthorized'}</p>;
};
- Use
getAppAuthManager
sign-in user
You can use the AuthManager
to perform some authorization-related actions or access the authorization state outside of React components
// SignInButton.tsx
import { isAxiosError } from 'axios';
import { getAppAuthManager } from 'utils/auth';
const SignInButton = () => {
const handleSignIn = async () => {
// Get AuthManager instance
const authManager = await getAppAuthManager();
try {
await authManager.signIn({ email: '[email protected]', password: 'p@$$w0rd' });
} catch (error) {
// The error you catch here will always be an AxiosError
// if you don't throw custom errors in `authOptions.signIn` function.
// So basically this check is needed only to narrow down the error type for TypeScript.
if (isAxiosError(error)) {
// API request failed. Display nice error message to the user
console.error(error.response?.data?.message);
}
console.error(error);
}
};
return <button onClick={handleSignIn}>Sign in</button>;
};
- Perform authenticated API calls
// UpdatePasswordButton.tsx
import axios from 'axios';
import { getAppAuthManager } from 'utils/auth';
const UpdatePasswordButton = () => {
const handleSignIn = async () => {
// Get AuthManager instance
const authManager = await getAppAuthManager();
// The axios instance you are using here will
// automatically add accessToken to the authorization header
// for more information see
const response = await authManager.axios.put('/profile', {
password: 'secret2',
});
};
return <button onClick={handleSignIn}>Update password</button>;
};
Gate pattern
You can use the Gate pattern to ensure that the authentication context is loaded before the page (or part of the page) becomes visible to the user.
// AuthGate.tsx
import { useAppAuthContext } from 'utils/auth';
const AuthGate = ({ children }: ReactPropsWithChildren<{}>) => {
const { isLoading, isSignedIn } = useAppAuthContext();
if (isLoading) return <Loader />;
// You might want to redirect user to login page here
if (!isSignedIn) return <p>Unauthorized!</p>;
// When children are rendered they will always have
// isLoading:false and isSignedIn:true set
return <>{children}</>;
};
Put AuthGate below AuthProvider to allow it access auth context.
You might also want to customize your AuthGate to handle access for different user roles.
// App.tsx
import { AuthProvider } from '@lavieennoir/auth/react';
import type { authOptions } from './auth-options';
const App = () => {
return (
<AuthProvider config={authOptions}>
<AuthGate>
<Profile />
</AuthGate>
</AuthProvider>
);
};
The component rendered below AuthGate will only be shown when user is authorized so you don't need to handle cases when auth manager is loading or user is unauthorized inside this component.
// Profile.tsx
import { useAppAuthContext } from 'utils/auth';
const Profile = () => {
const { user } = useAppAuthContext();
return <p>Email: {user.email}</p>;
};
The Profile
page will have no content until the AuthManager is loaded which leads to poor SEO. So this approach is only acceptable for pages that unauthorized users cannot view.
SSR support
It is possible to perform authorization check on server side (eg. when you use NextJS for server side rendering).
To achieve this you will need to implement custom storage class based on Cookies to make persistent auth storage accessible from both frontend and backend.
Add your CookieStorage class to authOptions object.
// utils/auth-options.ts
export const authOptions = {
storage: new CookieStorage()
...
}
- Get necessary user information on the server side. And pass it into the AuthProvider to make auth instantly accessible on client (without loading state).
// pages/_app.tsx
import auth from '@lavieennoir/auth';
import type { AppProps } from 'next/app';
import type { authOptions } from './auth-options';
const App = ({ Component, pageProps }: AppProps) => {
return (
<AuthProvider config={authOptions} initialState={pageProps.initialAuth}>
<AuthGate>
<Component {...pageProps} />
</AuthGate>
</AuthProvider>
);
};
export const getInitialProps = async () => {
// Imperatively create a new AuthManager instance on a backend
// to get current authData
const authManager = auth.createAuthManagerInstance(authOptions);
// return auth data to the frontend
return { initialAuth: authManager.getAuthData() };
};
export default App;
Default TypeScript use case (without React)
If you will only import code from @lavieennoir/auth
path and not @lavieennoir/auth/react
the bundle will not include any React related code and it will not require React as a peer dependency
Firstly, you need to complete Steps 1 and 2 from Default ReactJS use case. To define interface you authorization manager will work with and options for authorization manager.
Apply defined options to the AuthManager and create typed version of
getAuthManager
function (similar to step 4 of Default ReactJS use case).
// utils/auth.ts
import { getAuthFactory, getAuthManager } from '@lavieennoir/auth';
import { authOptions } from 'utils/auth-options';
import type { IUser, ISignInParams } from 'utils/auth-options.types';
getAuthFactory<IUser, ISignInParams>().setGlobalAuthOptions({
...authOptions,
// This option will force auth manager
// to refresh token after initialization
refreshTokenOnInit: true,
});
// Use throughout your app instead of plain `getAuthManager`
export const getAppAuthManager = () => getAuthManager<IUser, ISignInParams>();
- Use
AuthManager
to sign user in and make authenticated API requests.
// app.ts
import { getAppAuthManager } from 'utils/auth';
const authManager = await getAppAuthManager();
await authManager.signIn({ email: '[email protected]', password: 'secret' }).catch((e) => {
// Handle API errors here
console.log(e);
});
// Here you are already logged in if no error was thrown.
// So you can make authenticated calls.
// Let's update current user's password
const response = await authManager.axios.put('/profile', {
password: 'secret2',
});
Update user data
There is a common use case when you want to update profile information. In case user update data that is present in storage of this library (eg profile image, email, username) you will need to update storage with new values.
In this case you will need to call updateUser method of AuthManager.
So the function that will update profile data both on the frontend and backend can look like this:
import { getAppAuthManager } from 'utils/auth';
const updateUserProfile = async (body: IUpdateUserProfileRequest) => {
const auth = await getAppAuthManager();
const { data } = await auth.axios.patch('/profile', body);
// This is the place where local state is updated.
auth.updateUser(data);
return data;
};
Advanced configuration 🛠
There are a bunch of optional configuration options provided by IAuthOptions
interface.
storage?: IStorage
Class passed to this field should implement IStorage
interface and it will be used to persist authentication data.
You can use LocalStorage
or MemoryStorage
classes from this package or create own storage implementation.
By default this package use localStorage
to persist auth data.
You can find a LocalStorage
class in this package that implements the IStorage
abstraction and it is used as a default value.
storageKeys?: IStorageKeys
You can specify own keys that will be used to put data into the storage object. It might be helpful if default keys are already used i your application.
Default keys are represented by the following object:
{
accessToken: '@l/auth/access',
refreshToken: '@l/auth/refresh',
user: '@l/auth/user'
}
You can import defaultAuthStorageKeys
object
from index file of this package to read this object.
buildAuthorizationHeader?(manager: IAuthManager<IUser, ISignInParams>): string | null;
This function generate a value that will be added to the Authorization
header when making API requests using authManager.axios
.
The function accepts current AuthManager as a parameter. It can be used to get some authorization data or perform API requests within this function. This method is called only once access token is changed.
This option has a default implementation.
It will take current auth token from auth manager
and build Bearer authorization header.
null
will be returned if there is no authorization token:
(manager: IAuthManager<IUser, ISignInParams>) => {
const token = manager.getAccessToken();
return token ? `Bearer ${token}` : null;
};
How tokens are refreshed
There is a separate RefreshTokenHandler
class that is responsible for updating authorization header and rotating access and refresh tokens.
This class is designed for internal use and should not be instantiated manually.
When AuthManager is initialized RefreshTokenHandler
adds a response interceptor to provided axios
instance. This interceptor checks if authorized request return status code 401. That means that access token expired and it should be refreshed.
The AuthManager.refreshToken
method is called to claim new access token. And after that the failed request is retried with a new access token. If the request fails again the current user is considered unauthorized. And AuthManager.signOut
is called.
Dependencies 🔗
axios
axios is used to perform API calls. axios versions from 0.17.0
and newer are supported (including [email protected]
).
react
If you are using React components[email protected]
or newer is required. The main reason is usage of React Hooks in the library.
Full API reference 📙
IAuthOptions
signIn(signInParams: ISignInParams, manager: IAuthManager<IUser, ISignInParams>): Promise<AxiosResponse<IAuthResult>>
This function that will be called
when authManager.singIn
is called to get IAuthResult
from the API.
This function should throw an error if sign in was not successful.
If you are using manager.axios
to make an API call to sign-in
endpoint inside this function it will throw an error automatically
for non-200 response status codes.
- parameter
signInParams
Data passed from the sign-in form. It should match ISignInParams
interface defined in your application.
- parameter
manager
Current AuthManager. It can be used to get some authorization data or perform API requests within this function.
- returns
It should return an axios result with an object that contain accessToken
and refreshToken
fields.
refreshToken(manager: IAuthManager<IUser, ISignInParams>): Promise<AxiosResponse<IAuthResult>>;
This function that will be called when accessToken expired and it needs to be refreshed.
- parameter
manager
Current AuthManager. It can be used to get some authorization data or perform API requests within this function.
getUser(manager: IAuthManager<IUser, ISignInParams>): Promise<AxiosResponse<IUser>>;
This function that will be called when user data is fetched (after sign-in or after token refresh)
- parameter
manager
Current AuthManager. It can be used to get some authorization data or perform API requests within this function.
signOut?(manager: IAuthManager<IUser, ISignInParams>): Promise<void>;
You can specify the function that will be called
when user signs out (by calling authManager.signOut
or when tokens expire).
for example it may be used to make an API call
to invalidate accessToken when user signs out.
- parameter
manager
Current AuthManager. It can be used to get some authorization data or perform API requests within this function.
- default value
By default this function does not perform any action:
() => Promise.resolve();
axiosInstance?: AxiosInstance;
axios instance passed here will be a base for authManager.axios
you will use further
usually it is enough to set base url and content type here. But if your server requires
any additional configuration to perform api request it can be done here.
After library initialization response interceptor will be added to this axios instance.
This interceptor will try to refresh the accessToken
if user is signed in and request returned
401 (Unauthorized) status code.
- default value
axios.create({ headers: { 'Content-Type': 'application/json' } });
storage?: IStorage;
Class passed to this field should implement IStorage
interface and it is used to persist authentication data.
You can use LocalStorage
or MemoryStorage
classes from this package
or implement own storage.
- default value
new LocalStorage();
By default this package use localStorage
to persist auth data.
You can find a LocalStorage
class in this package that implements
the IStorage
abstraction and it is used as a default value.
buildAuthorizationHeader?(manager: IAuthManager<IUser, ISignInParams>): string | null;
This function generate a value that will be added to the Authorization
header when making API requests using authManager.axios
.
- parameter
manager
Current AuthManager. It can be used to get some authorization data or perform API requests within this function.
- default value
This option has a default implementation. It will take current auth token from auth manager and build Bearer authorization header. null will be returned if there is no authorization token:
(manager: IAuthManager<IUser, ISignInParams>) => {
const token = manager.getAccessToken();
return token ? `Bearer ${token}` : null;
};
storageKeys?: IStorageKeys;
You can specify own keys that will be used to put data into the storage object. It might be helpful if default keys are already used i your application.
- default value
{
accessToken: '@l/auth/access',
refreshToken: '@l/auth/refresh',
user: '@l/auth/user',
}
You can import defaultAuthStorageKeys
object
from index file of this package to access this object.
ISignedInOptions
This interface is used in initialState
property of AuthProvider and AuthFactory.createAuthManagerInstance.
isSignedIn: boolean;
Determines whether the user is signed in.
accessToken: IsSignedIn extends false ? null : string;
Contain user's access token
or null
if user is not signed in.
refreshToken: IsSignedIn extends false ? null : string;
Contain user's refresh token
or null
if user is not signed in.
user: IsSignedIn extends false ? null : IUser;
Contain user's information determined by IUser
interface
or null
if user is not signed in.
IAuthFactoryOptions
These options are used to initialize AuthManager when calling getAuthManager
.
refreshTokenOnInit?: boolean;
When set to true, forces getAuthManager
function to refresh user token in case it create new auth manager.
This may be useful to initialize authManager during SPA mount and make sure user have access to the page.
initialState?: ISignedInOptions;
You can pass initial state to the authManager so it can have initial signed-in or signed-out state without having a loading state.
When passed, AuthManager will have this data accessible right after initialization
IGlobalAuthOptions
This is just combination of IAuthOptions and IAuthFactoryOptions.
IStorage
Represent the abstraction for user data persistent storage.
This interface can be implemented to store user auth data wherever you want instead of default localStorage
implementation.
setItem<T>(key: string, data: T): void;
getItem<T>(key: string): T;
remove(key: string): void;
multiSet<T extends {}>(data: T): void;
multiRemove(keys: string[]): void;
IAuthManager
axios: AxiosInstance;
Instance of axios you that contain interceptors to perform authorized requests. When using this axios instance, the Authorization header is automatically added to requests.
options: Readonly<Required<IAuthOptions<IUser, ISignInParams>>>;
Options you have passed during initialization of AuthManager.
getUser(): IUser | null;
Returns IUser
model if the user is logged in or null
otherwise.
getAccessToken(): string | null;
Returns accessToken
if the user is logged in or null
otherwise.
getRefreshToken(): string | null;
Returns refreshToken
if the user is logged in or null
otherwise.
getAuthorizationHeader(): string | null;
Returns formatted http header value generated by options.buildAuthorizationHeader
.
getAuthData(): IAuthData<IUser>;
Returns combined data from getUser
, getAccessToken
, getRefreshToken
, isSignedIn
methods.
setAuth(user: IUser, authResult: IAuthResult): this;
This method will save the authentication token and the user information in the storage to emulate sign-in action.
This method emits AuthEventNames.onAuthStateChanged
& AuthEventNames.onSignedIn
events.
updateUser(user: Partial<IUser>): this;
Updates the user data in the storage. This function Do not perform API request to update user on the Backend it just update user data in a storage locally.
This method emits AuthEventNames.onAuthStateChanged
event.
It only updates the fields passed as parameter instead of rewriting entire IUser
model so it can be partially updated.
isSignedIn(): boolean;
Returns true
if the user is signed in.
signIn(signInParams: ISignInParams): Promise<this>;
This method will call the signIn
method from IAuthOptions
and then save the authentication token and get the user information. All received data is stored in the storage.
The returned promise will be rejected if sign in attempt failed and resolve with AuthManager object if it was successful.
This method emits AuthEventNames.onAuthStateChanged
, AuthEventNames.onSignedIn
, AuthEventNames.onSignInFailed
events.
signOut(): Promise<this>;
This method will call the signOut
method from IAuthOptions
and then clear the authentication token and all user information.
refreshToken(token?: string): Promise<this>;
This allow you to manually refresh token in the storage. When token
parameter is passed it will be used to perform refresh token API call. Otherwise the current refresh token (that is present in the storage) will be used.
This method emits AuthEventNames.onAuthStateChanged
& AuthEventNames.onTokenRefreshed
events.
isDisposed(): boolean;
Returns true if dispose method was called on this instance.
dispose(): void;
Removes all active event listeners on this object. Remove response interceptor that refreshes token from axios instance. Instance can not be used after disposing! You this method to clean up resources or if you are going to reinitialize AuthManager.
onSignedIn(callback: AuthCallback<IUser, ISignInParams>): AuthCallbackUnsubscriber;
Callback is triggered when user sign-in attempt was successful.
AuthManager
object is passed as a callback parameter.
onSignInFailed<IData = unknown, IConfigData = unknown>(callback: AuthResponseCallback<IData, IConfigData>): AuthCallbackUnsubscriber;
Callback is triggered when user sign-in attempt was not successful.
AxiosResponse
object is passed as a callback parameter.
onSignedOut(callback: AuthCallback<IUser, ISignInParams>): AuthCallbackUnsubscriber;
Callback is triggered when signOut
method was successfully executed.
AuthManager
object is passed as a callback parameter.
onTokenRefreshed(callback: AuthCallback<IUser, ISignInParams>): AuthCallbackUnsubscriber;
Callback is triggered when refreshToken
method was successfully executed. Or when expired token was automatically refreshed by AuthManager.
AuthManager
object is passed as a callback parameter.
onStateChanged(callback: AuthCallback<IUser, ISignInParams>): AuthCallbackUnsubscriber;
Callback is triggered when:
- user sign in was successful or unsuccessful;
- when user signed out;
- when token is refreshed;
- when user data is updated.
So it is basically called on any change in AuthManager state.
AuthManager
object is passed as a callback parameter.
AuthFactory
static createAuthManagerInstance<IUser, ISignInParams>(authOptions: IAuthOptions<IUser, ISignInParams>): IAuthManager<IUser, ISignInParams>;
Creates new instance of AuthManager with passed IAuthOptions
object.
static createAuthManagerInstance<IUser, ISignInParams, IsSignedIn extends boolean>(authOptions: IAuthOptions<IUser, ISignInParams>,signedInOptions: ISignedInOptions<IsSignedIn, IUser>): IAuthManager<IUser, ISignInParams>;
Creates new instance of AuthManager with passed IAuthOptions
object and hydrates it with predefined state to avoid async initialization.
setGlobalAuthOptions = (authOptions: IGlobalAuthOptions<IUser, ISignInParams> | null): void
Set options that will be used to create a singleton instance of AuthManager accessible using getAuthManager
method.
hasGlobalAuthOptions(): boolean;
Returns true
when global options are not equal to null
. false
otherwise.
isAuthManagerInitialized(): boolean;
After this check you can be sure that calling tryGetAuthManager
will not throw an error. Returns true
when AuthManager singleton is initialized.
tryGetAuthManager(): AuthManager;
Try to get singleton instance of AuthManager without waiting for initialization. If AuthManager is not yet initialized this method will thor an error with a message "AuthManager is not initialized!". Otherwise it returns instance of AuthManager.
getAuthManager(): Promise<AuthManager>
Return singleton instance of AuthManager if exist.
Otherwise it will try to create new instance using global options.
Global options must be set before calling this method using setGlobalAuthOptions
.
This method throws an Error with a message "getAuthManager() method of Auth cannot be called before setAuthOptions(). Options are required to create auth manager." if auth options are not set.
disposeAuthManager(): void;
Removes all active event listeners on AuthManager singleton object. Remove response interceptor that refreshes token from axios instance. AuthManager instance should not be used after disposing! The next call to getAuthManager will try to create a new instance.
Call of this function does not reset auth options
set via setGlobalAuthOptions
! You need to update them manually.
getAuthFactory
This method is used for React + TypeScript projects only.
Returns AuthFactory class instance typed with generic parameters applied to this function.
getAuthManager
This method is used for React + TypeScript projects only.
Returns IAuthManager instance typed with generic parameters applied to this function.
It will try to initialize an auth manager if it was not yet initialized
This function is an alias for getAuthFactory().getAuthManager()
useAuthContext
This method is used for React projects only.
This hook returns values stored in AuthProvider
.
isLoading: boolean;
Returns ture
if AuthManager is not yet initialized.
Values of other properties is not guaranteed and should not be accessed while isLoading
is true
.
user: IUser | null;
Returns user data if isSignedIn
is true
. Returns null
otherwise.
accessToken: string | null;
Returns current access token if isSignedIn
is true
. Returns null
otherwise.
refreshToken: string | null;
Returns current refresh token if isSignedIn
is true
. Returns null
otherwise.
isSignedIn: boolean;
Returns true
user is signed in.
AuthProvider
All the components that execute useAuthContext
hook should be located below AuthProvider
in the component tree.
Under the hood, React Context is used to implement this function. So you can refer to regular React context concepts when using AuthProvider
.
Props:
refreshTokenOnInit?: boolean;
See IAuthFactoryOptions for more details.
initialState?: ISignedInOptions;
See IAuthFactoryOptions for more details.
config: IAuthOptions<IUser, ISignInParams, IStorageKeys>;
Auth options used to initialize AuthManager. See IAuthOption for more details.
children: ReactNode;
Components to render inside AuthProvider
.
Known issues 🚧
Using endpoint that return different data for authorized and unauthorized users.
In this case, the library will not know that the access token has expired because the authorized endpoint did not return an unauthorized response error code.
You can see an example express
app endpoint that represent this problem:
app.get('/hello', (req, res) => {
if (!!req.user) {
// return message if user is authenticated
return res.json({ message: `Hello ${user.name}` });
}
// return another message if user is not authenticated
// instead of `res.status(401)` Unauthorized response error code
return res.json({ message: `Hello anonymous` });
});
In this case, the library will not know that the access token has expired.
There are 2 ways to solve this issues:
- You may split the endpoints into 2: one for the public access and another one for the authorized access as shown below
// private endpoint
app.get('/hello/authorized', (req, res) => {
if (!req.user) {
return res.status(401).json({ message: 'Unauthorized' });
}
return res.json({ message: `Hello ${user.name}` });
});
// public endpoint
app.get('/hello', (req, res) => {
return res.json({ message: `Hello anonymous` });
});
- Create middleware that will throw 401 error if access token is passed and is invalid:
const authorizedMiddleware = (req, res, next) => {
if (!req.headers.authorization) {
// endpoint will be handled as a public if Authorization header is not set
return next();
}
// let's assume `isAuthHeaderValid` function is implemented above
// and it returns true if token is valid and not expired
if (isAuthHeaderValid(req.headers.authorization)) {
return next();
}
return res.status(401).json({ message: 'Unauthorized' });
};
app.get('/hello', authorizedMiddleware, (req, res) => {
if (!!req.user) {
return res.json({ message: `Hello ${user.name}` });
}
return res.json({ message: `Hello anonymous` });
});
Road Map 🛣️
1. CookieStorage
for full SSR support
We will provide CookieStorage
implementation as a part of the package to make it easer to configure SSR authorization.
2. Preventive update of tokens
Tokens will be automatically renewed before calling the API if they are about to expire.
3. Customizable handler for refresh token process
This feature will allow you to override refresh token process and use any HTTP client of your choice instead of axios
for API calls.
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.