Two-Factor Authentication Plugin
The Two-Factor Authentication Plugin provides an additional layer of security to the application by requiring users to provide a second form of authentication in addition to their password. This plugin supports authenticator apps.
Installationβ
npm i @adminforth/two-factors-auth --save
Plugin is already installed into adminforth, to import:
import TwoFactorsAuthPlugin from '@adminforth/two-factors-auth';
Plugin required some additional setup, to make it work properly. It should be added to the resource auth resource. In our example we will add it to the user resource .
model users {
id String @id
created_at DateTime
email String @unique
role String
password_hash String
secret2fa String?
}
Then:
npx --yes prisma migrate dev --name init
And add it to users.ts
{
table: 'users',
plugins: [
new TwoFactorsAuthPlugin ({ twoFaSecretFieldName: 'secret2fa', timeStepWindow: 1 }),
],
columns: [
...
{
name: 'secret2fa',
showIn: [],
backendOnly: true,
}
],
}
π‘ Note: Time-Step Size
By default,
timeStepWindow
is set to1
, which means the Two-Factor Authentication Plugin will check the current 30-second time-step, as well as one step before and after, to validate a TOTP code. This aligns with RFC 6238 best practices to accommodate slight clock drift between the server and the user's device.For example, if a code is generated between 12:00:00 and 12:00:30, it will typically expire at 12:00:30. However, with a
timeStepWindow
of1
, the plugin will continue to accept it up to 12:00:59 (the βnextβ 30-second step), preventing users from being locked out if their device clock is a few seconds off. Once the clock hits 12:01:00, that previous code will be treated as expired.If you find users frequently encountering code mismatches due to clock drift, you can increase
timeStepWindow
to2
. However, be cautious: larger windows can reduce overall security!β With a
timeStepWindow
set to0
, the plugin will pass all the expired codes, which is not secure and should only be used for testing purposes.
Thats it! Two-Factor Authentication is now enabled:
Disabling Two-Factor Authentication locallyβ
If it is not convenient to enter the code every time you log in during local development, you can disable Two-Factor Authentication
for the dev environment using usersFilterToApply
option.
plugins: [
new TwoFactorsAuthPlugin ({
twoFaSecretFieldName: 'secret2fa',
usersFilterToApply: (adminUser: AdminUser) => {
// if this method returns true, 2FA will be enforced for this user, if returns false - 2FA will be disabled
if (process.env.NODE_ENV === 'development') {
return false;
}
return true;
},
}),
],
Select which users should use Two-Factor Authenticationβ
By default plugin enforces Two-Factor Authentication for all users.
If you wish to enforce 2FA only for specific users, you can again use usersFilterToApply
option:
usersFilterToApply: (adminUser: AdminUser) => {
// disable 2FA for users which email is 'adminforth' or 'adminguest'
return !(['adminforth', 'adminguest'].includes(adminUser.dbUser.email));
},
You can even add a boolean column to the user table to store whether the user should use 2FA or not:
{
resourceId: 'users',
...
columns: [
...
{
name: 'use2fa',
}
...
],
options: {
allowedActions: {
delete: ({ adminUser }: { adminUser: AdminUser }) => {
// only superadmin can delete users
return adminUser.dbUser.role === 'superadmin';
},
create: ({ adminUser }: { adminUser: AdminUser }) => {
// only superadmin can create users
return adminUser.dbUser.role === 'superadmin';
},
edit: ({ adminUser, meta }: { adminUser: AdminUser }) => {
// user can modify only his own record
const { oldRecord } = meta;
return adminUser.dbUser.id === oldRecord.id;
},
}
},
plugins: [
new TwoFactorsAuthPlugin ({
twoFaSecretFieldName: 'secret2fa',
usersFilterToApply: (adminUser: AdminUser) => {
return adminUser.dbUser.use2fa;
},
}),
],
}
Allow Specific Users to Skip Two-Factor Authentication Setupβ
By default, all users are required to setup Two-Factor Authentication.
If you want to allow specific users to skip the 2FA setup, you can use the usersFilterToAllowSkipSetup
option:
...
plugins: [
new TwoFactorsAuthPlugin ({
twoFaSecretFieldName: 'secret2fa',
...
usersFilterToAllowSkipSetup: (adminUser: AdminUser) => {
// allow skip setup 2FA for users which email is 'adminforth' or 'adminguest'
return !(['adminforth', 'adminguest'].includes(adminUser.dbUser.email));
},
}),
],
...