This is the frontend library for the AI-KIT module Authentication.
Use it in conjunction with the django package django-ai-kit-auth
in order to get a
functioning authentication running in your app in no time.
You can easily install AI-KIT: Authentication via npmjs. Using npm, run
npm install ai-kit-auth
Using yarn, run
yarn add ai-kit-auth
ai-kit-auth
has a number of peer dependencies that you need to install yourself before you get started:
react@^16.8.0
react-router-dom@^5.0.0
Necessary only if you want to use the react router confignext@^10.0.0
Necessary only if you want to use the next js config@material-ui/core@^4.9.0
Necessary only if you want to use the UI-components@material-ui/icons@^4.9.0
Necessary only if you want to use the UI-components
While it is possible to customize many aspects of appearance and behaviour,
the fastest way to get a functioning authentication module is to use a default
configuration provided by this library.
If you use react-router
, you can create a configuration file like this:
// configureAuth.ts
import { configureAuthReactRouter } from 'ai-kit-auth';
export const {
UserStore,
useUserStore,
makeAuthRoutes,
ProtectedRoute,
} = configureAuthReactRouter({
api: {
url: 'http://localhost:8000/api/v1/',
authPath: 'auth/',
}
});
Then you can set up your App.tsx
(or App.jsx
) like this:
import React from 'react';
import { BrowserRouter, Switch } from 'react-router-dom';
import { CssBaseline, ThemeProvider } from '@material-ui/core';
import { DefaultTheme } from 'ai-kit-common';
import { UserStore, makeAuthRoutes, ProtectedRoute } from './configureAuth';
import MainPage from './components/MainPage';
const App: React.FC = () => (
<>
<CssBaseline />
<ThemeProvider theme={DefaultTheme}>
<UserStore>
<BrowserRouter>
<Switch>
{
// Create routes for login, registration, password reset etc.
makeAuthRoutes()
}
<ProtectedRoute exact path="/" component={MainPage} />
</Switch>
</BrowserRouter>
</UserStore>
</ThemeProvider>
</>
);
export default App;
Let's break this down: the most important component is UserStore
, which
stores user data and provides login/logout etc. functions.
makeAuthRoutes
returns a list of important routes which enable
basic authentication functionality.
ProtectedRoute
provides a quick way to force users to log in if
they want to access specific pages.
See the API reference for more information.
In order to guard against CSRF attacks, the UserStore
obtains and provides a csrf-token for
you to use.
You can obtain it from the useUserStore
hook and add it to each 'unsafe' request (anything
that is not OPTIONS
, GET
, HEAD
or TRACE
) as X-CSRFToken
-header.
If you use axios
, you can use the axiosRequestConfig
provided by useUserStore
, which already
contains the CSRF token in the correct header.
The components were designed with the AI KIT DefaultTheme theme in mind.
To use it, add ai-kit-common
to your project,
wrap your application in <ThemeProvider>
tags and add the required fonts to your application.
You are also advised to add a <CssBaseline />
component to your application's root to 'normalize' the browser styles.
Of course, you are also free to use whatever custom Material Theme you wish.
AI-KIT: Authentication provides the following components and functions:
- Configuration
- UserStore
- Login
- Registration
- Forgot Password
- Reset Password
- Email-Activation
- Errors
- ReactRouter
- Next JS
This is the general function to create customized components for authentication
pages as well as the UserStore.
However, it does not have a default configuration for routing (or jumping between pages).
These defaults are implemented in the respective configuration functions for
different routing libraries.
If you use react-router
, use the configureAuthReactRouter function instead.
In case you use next
, configureAuthNext is the way to go.
- Type parameter
UserType
: tells typescript, what kind of user object you expect from the backend. If you want to load more data than just the username and email of a user, you need to configure your django backend to use a customUserSerializer
. Then you need to describe the data structure in a customUser
interface, which you pass to this function. config: InputConfig
: a configuration object with information about route paths, components etc. Besides a deepPartial
ofdefaultConfig
's type, this type also has mandatory fields regarding the routing and backend api:
routing: {
link: React.ComponentType<LinkProps>; // Component to display whenever a hyperlink is needed
useRouteHandler: () => RouteHandler; // A React hook which gives an object containing
// push and replace functions for the browser's history
useQueryParams: () => Record<string, string>; // A React hook which gives an object containing the urls'
// query parameters on a key=value basis.
};
api: {
url: string; // tells the store, where to send login requests.
// This should be the url to the django backend
// of your project including `api/v1/` or similar.
authPath: string; // The path at which the auth routes are available in the backend.
// If you included the ai-kit-auth paths as
// `path(r"api/v1/auth/", "ai_kit_auth.urls"),` in your django app,
// you must set `apiAuthPath` to `auth/`./
};
}
If you are absolutely certain that you need to use this function, please take a look at the source code for more information.
An object containing custom versions of all this modules' common components.
UserContext
!
This object contains default values for AI-KIT: Auth configuration.
export const defaultConfig = {
paths: {
mainPage: '/', // login redirects here by default
base: '/auth', // this path will be prepended to all other paths
login: '/login',
register: '/register',
activation: '/activation', // email activation path
forgotPassword: '/forgot-password', // clicking 'forgot password' on the login
// page leads here
resetPassword: '/reset-password', // actual page to reset the password.
// Only accessible via link, which is sent by email.
emailSent: '/email-sent', // success feedback after email was sent from the
// forgot password page
},
defaultTranslator: en, // A 'Translator' function, responsible for mapping keys
// to user facing strings.
userIdentifier: Identifier.UsernameOrEmail, // what should the user type in the
// login screen?
disableUserRegistration: false, // setting this to true will remove the register
// path completely
components: {
backgroundImage: DefaultBackgroundImage,
loadingIndicator: CircularProgress,
// is shown while user info is retrieved from server
},
};
See Translator
.
The backgroundImage
is used in all the views that are wrapped in AuthView
.
The CircularProgress
component is imported from Material-UI.
Ai-Kit-Auth components use a translator function for displaying user-facing strings.
Therefore, the config accepts translator: Translator
as parameter.
This type is defined as (key: string) => string
and will receive strings of the form
auth:Path.To.Key
, conforming with i18next.
If you don't need dynamic translation and are just interested in a different supported
language than the default (English), you can pass one of the predefined translator
functions exported by ai-kit-auth
. Currently available choices are en
and de
.
If you need to translate only a few strings differently, we would advise you to inspect
the source code in order to find the correct keys and write a wrapper function around
one of the predefined translators in order to intercept the translation of those keys.
import { configureAuth, en } from 'ai-kit-auth';
const customKey = 'auth:Common.ok';
const customValue = `Okay, Okay!`;
const t = (key: string) => {
if (key === customKey) return customValue;
return en(key);
};
export const {
UserStore, ProtectedRoute, makeAuthRoutes,
} = configureAuth({ translator: t });
If you would like to use dynamic translations, we suggest to use i18next
and to pass your translator function t
to makeAuthRoutes
if you use react-router
or the AuthPage
component if you use next
.
That way, auth components are re-rendered whenever the language changes.
In that case, you need to provide all the translations yourself in the namespace auth
.
To get started, you can copy the .json
files containing our translations from the
dist/internationalization
folder of this module.
import { useTranslation } from 'react-i18next';
import { UserStore, makeAuthRoutes } from './configureAuth';
...
const App: FC = () => {
const { t } = useTranslation(['auth']);
return (
<UserStore>
<Switch>
{makeAuthRoutes(t)}
</Switch>
</UserStore>
);
};
Using this enum you can control with which information the user can login. Its values are:
Identifier.Username
: login only with usernameIdentifier.Email
: login only with email address and remove username field from register formIdentifier.UsernameOrEmail
: login with either username or email address (default)
This function creates a UserStore
component function, as well as
UserContext
for accessing user data in class-based components,
MockUserStore
for testing purposes,
and useUserStore
, a hook for accessing user data in functional components.
Use this function if you only want to use the login, register, etc. functions without the
material-ui components.
- Type parameter
UserType
: tells typescript, what kind of user object you expect from the backend. If you want to load more data than just the username and email of a user, you need to configure your django backend to use a customUserSerializer
. Then you need to describe the data structure in api: { url: string; authPath: string }
: a config object with information about how to reach the backend. For more detailed information seemakeComponents
.
UserContext
!
This component provides user data and authentication helper functions to its children via react context. For AI-KIT: Authentication to work correctly, it is necessary to place it high in your component tree, so that it contains any components which might try to access user information, or perform user actions like login, logout etc.
initialUser?: User
: An object of theUser
type provided as type parameter to the configuration function. If no such object is provided, theUserStore
will try to load user information from the backend. This prop could be used if you want to do server side rendering and obtained the user info in an asyncgetServerSideProps
call.initialCsrf?: string
: If you provide aninitialUser
object, you should also provide the initial csrf token which you obtained when you obtained the user object. Otherwise it might happen that unsafe calls without csrf token are blocked by the backend.
// App.tsx
import React from 'react';
import { UserStore } from './configuredAuth';
import ...
const App: React.FC = () => (
<UserStore>
<BrowserRouter>
...
</BrowserRouter>
</UserStore>
);
export default App;
This component provides a mocked UserStore
for testing purposes.
It provides the same context that UserStore
does, however,
all functions provided here return promises that never resolve.
This is a react Context
and can be used as such. Its Consumer will receive the following fields:
apiUrl: string
: contains the url of the backend provided in the configuration.csrf: string
: a token obtained from the server, used to guard against Cross Site Request Forgeries. Whenever you make aPOST
,DELETE
,PATCH
orPUT
request to our backend, you need to place this token in theX-CSRFToken
header of the request. Otherwise, the request will be rejected (provided that CSRF is enabled on the backend).axiosRequestConfig
: An object you can pass to axios calls asconfig
object. It includes the backend url (apiUrl
),withCredentials: true
and the CSRF token as header.loading: boolean
: true if a login request has been sent, but the reply has not yet arrived.login: (userIdentifier: string, password: string) => Promise<void>
: triggers a login request. It requires an identifier string, which is either the username or email of the user. Depending on the configuration, the backend accepts either one or only one of them.loggedIn: boolean
: an indicator, whether the user is logged in. For this check, you can either use thisboolean
, or you check ifuser
ofUserContext
isnull
.logout: () => Promise<void>
: triggers a logout request. If successful, it removes the cookies and sets theuser
inUserContext
tonull
.justLoggedOut: boolean
: Is set to true after a successfullogout
. However, it is not persistent, so after the next page refresh, it will be set tofalse
again. It is used to display a non-persistent notification that the logout was successful on the login page.updateUserInfo: () => Promise<void>
: triggers a request to update both the user object and the CSRF-token. It can be used to obtain the the user itself.register: (username: string, email: string, password: string) => Promise<void>
: triggers a request to register a new user. If successful, the server will have sent an email to the email provided as parameter. If unsuccessful, it will raise an exception containing information about which fields need to be corrected. If there are errors not pertaining to a single field, they will be placed innon_field_errors
.activateEmailAddress: (userIdentifier: string, token: string) => Promise<void>
: triggers a request to validate a user's email address and activate their account. If successful, the user in question is able to login.requestPasswordReset: (email: string) => Promise<void>
: triggers a request to the password reset endpoint, which sends an email with a link to the provided email, if that email indeed belongs to a user in the database. However, the backend's response is positive regardless, of whether the email is in the database or not.resetPassword: (ident: string, token: string, password: string) => Promise<void>
: triggers a request to reset a users password. If successful, the promise will be resolved. If unsuccessful, it will raise an exception containing information about which fields need to be corrected.
This is a react Context
and can be used as such. Its Consumer will receive the following fields:
user: { id: number; username: string; email: string }|undefined
You can configure the type of user
with makeComponents
,
makeGenericUserStore
,
configureAuthReactRouter
or
configureAuthNext
.
If it is undefined, the user is not logged in.
useUserStore
is a react hook, which can be used to obtain user information and helper
functions stored in the UserStore
.
It returns an object containing the user
object provided by UserContext
,
as well as all the fields that AuthfunctionContext
provides.
Styled page wrapper for a LoginForm.
translator?: Translator
: A function which maps keys to user facing strings.
const App: React.FC = () => (
<UserStore>
<LoginView />
</UserStore>
);
LoginForm
is a react component that provides a
Material UI Paper wrapper and contains two
input fields (username/email and password) and a submit button.
There are also links to registration and password recovery.
translator?: Translator
: A function which maps keys to user facing strings.
Styled page wrapper for a RegisterForm.
translator?: Translator
: A function which maps keys to user facing strings.
const App: React.FC = () => (
<UserStore>
<RegisterView />
</UserStore>
);
RegisterForm
is a react component that provides a
Material UI Paper wrapper and contains three
input fields (username, email and password) and a submit button.
There is also a link to the login page.
translator?: Translator
: A function which maps keys to user facing strings.
This component analyses the current URL to find the user identifier and email activation
token, which are needed to activate a user's email address.
If found, they are sent to the /activate_email/
endpoint of the backend and the result
of that request is rendered as error or success view. While waiting for the request,
a loading indicator is shown.
This component needs to be placed within a Route
so the the query parameters ident
and token
can be read.
It also needs a UserStore
as parent somewhere in the tree, so that it can find the
activateEmailAddress
function.
translator?: Translator
: A function which maps keys to user facing strings.
<Route
exact
path={`${authPath}/activation/`}
component={ActivateEmailAddress}
key="activation"
/>
Be aware that ActivateEmailAddress
should not reside inside a ProtectedRoute
, as it needs to be
accessible to users who are not logged in.
Styled page wrapper for a ForgotPasswordForm.
translator?: Translator
: A function which maps keys to user facing strings.
const App: React.FC = () => (
<UserStore>
<ForgotPasswordView />
</UserStore>
);
ForgotPasswordForm
is a react component that provides a
Material UI Paper wrapper and contains one
input field (email) and a submit button. Upon submit the requestPasswordReset()
method of the AuthFunctionContext is called.
There is also a link to the login page.
translator?: Translator
: A function which maps keys to user facing strings.
Styled page wrapper for a ResetPasswordForm.
translator?: Translator
: A function which maps keys to user facing strings.
const App: React.FC = () => (
<UserStore>
<ResetPasswordView />
</UserStore>
);
ResetPasswordForm
is a react component that provides a
Material UI Paper wrapper and contains two
input fields (password and password-repeat) and a submit button. Upon submit the resetPassword()
method of the AuthFunctionContext is called.
translator?: Translator
: A function which maps keys to user facing strings.
This function provides default values to the mandatory ones of makeComponents
.
It uses the widely used react-router
library to define functionality for page transitions.
The parameters are identical to those of makeComponents
, with the exception that
there is no routing
field.
An object containing all components returned by makeComponents
, as well as
import { configureAuthReactRouter } from 'ai-kit-auth';
export const {
UserStore, useUserStore, makeAuthRoutes, ProtectedRoute,
} = configureAuthReactRouter({
api: {
url: 'http://localhost:8000/api/v1/',
authPath: 'auth/',
},
});
This function is the fastest way to get started with AI-KIT and react-router
.
It returns an array of all the necessary frontend routes and views for authentication,
like login, registration, password reset etc.
It creates a Route
for each entry in defaultConfig
.paths
,
except for mainPage
and base
.
If you configure disableUserRegistration: true
, the register
path will not get a route.
Call this function in your App
component inside a Switch
.
translator?: Translator
: A function which maps keys to user facing strings.
A list of JSX.Element
s (Routes
), which can be placed directly in a react-router-dom
Switch
.
import React from 'react';
import { makeAuthRoutes } from 'ai-kit-auth';
import ...
const App: React.FC = () => (
<UserStore>
<BrowserRouter>
<Switch>
{makeAuthRoutes()}
<Route ... />
...
</Switch>
</BrowserRouter>
</UserStore>
);
export default App;
A wrapper for <Route> routes that
should only be available to users that are logged in.
It checks with the UserContext if the user is in fact logged in. If not, it will redirect to
the login screen (default: /auth/login
).
During the check a loading indicator is shown.
<UserStore>
<BrowserRouter>
<Switch>
<ProtectedRoute exact path="/">
<div>
Hello World
</div>
</ProtectedRoute>
</Switch>
</BrowserRouter>
</UserStore>
Use this wrapper for <Route>
as Route for a login page, if you are not using makeAuthRoutes
.
It uses the AuthFunctionContext
to see if a user is logged in or not.
When the user is logged in it redirects to it's referrer.
If there is no referrer, it redirects to the main page
(default '/'
).
<UserStore>
<BrowserRouter>
<Switch>
<LoginRoute exact path="/auth/login" component={LoginView} />
</Switch>
</BrowserRouter>
</UserStore>
This function provides default values to the mandatory ones of makeComponents
.
It uses the very popular next.js framework for routing.
The parameters are identical to those of makeComponents
, with the exception that
there is no routing
field.
An object containing all components returned by makeComponents
, as well as
import { configureAuthNext } from 'ai-kit-auth';
export const {
UserStore, useUserStore, makeAuthRoutes, ProtectedRoute,
} = configureAuthNext({
api: {
url: 'http://localhost:8000/api/v1',
authPath: 'auth/',
},
});
You can use this component to render all authentication pages using a dynamic route.
For this to work, simply export it from a file [authpage].tsx
or [authpage].jsx
in the folder pages/auth/
in your next.js project, or, if you configured paths.base
,
in the folder pages/<paths.base>/
.
t?:
Translator
If you work with next-i18next, you can use theappWithTranslation
HOC or theserverSideTranslation
to provide a translator function, which is passed to all dynamic pages automatically.
import React from 'react';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { AuthPage } from '../../src/configureAuth';
export default AuthPage;
export const getStaticProps = async ({ locale }) => ({
props: {
...await serverSideTranslations(locale, ['auth']),
},
});
You can wrap content, that requires login, in this component in order to send visitors who are not logged in straight to the login page when they try to see the wrapped content. If you do not provide user info during server side rendering, a loading indicator will be rendered instead of the content.
import React from 'react';
import { useUserStore, PrivateProtection } from '../configureAuth';
export const MyComponentThatNeedsAUser: React.FC => () => {
const { user } = useUserStore();
return (
<PrivateProtection>
Hello, {user?.username}
</PrivateProtection>
);
};
This component is a wrapper for LoginView
and redirects to the previous page or
the main page in case the user is already logged in.
If you use AuthPage
, you will not need this component.
translator?:
Translator
const MyPage = () => (
<UserStore>
<LoginComponent />
</UserStore>
)