Support Two Factor Authentication (2FA) in your application with ease.
Create and verify cryptographically secure Time-based One-time Passwords
(TOTP) using the HMAC-based One-time Password (HOTP) algorithm.
npm install @epic-web/totp
The problem
You want to support 2FA clients or generate safe one-time passwords to otherwise
verify your users.
This solution
This was copy/paste/modified/tested from notp (MIT)
The primary motivation was to support a more secure algorithm than SHA1
(though Google Authenticator only supports SHA1
, longer-lived OTPs should use
a more secure algorithm). The maintainer has not actively responded to issues or
pull requests in years.
Some improvements were made to modernize the code (which was last published
in 2014) and improve the API. But the core algorithm is unchanged.
Terms
- OTP: One Time Password
- HOTP: HMAC-based One Time Password
- TOTP: Time-based One Time Password
The TOTP is what we typically use for verification codes. This can be used
for 2FA (two-factor authentication), but also used for email verification,
password reset, etc.
Usage
This package exports three methods:
generateTOTP
- This generates the OTP and returns the config used to
generate it.verifyTOTP
- This verifies the OTP against the config used to generate it.getTOTPAuthUri
- This generates a URI that can be used to add the OTP to an
authenticator app.
2FA code
Here's the typical process for generating a 2FA auth URI (which the user can add
to their authenticator app).
import { generateTOTP, getTOTPAuthUri, verifyTOTP } from '@epic-web/totp'
const { secret, period, digits, algorithm } = generateTOTP()
const otpUri = getTOTPAuthUri({
period,
digits,
algorithm,
secret,
accountName: user.email,
issuer: 'Your App Name',
})
const code = await getCodeFromUser()
const isValid = verifyTOTP({ otp: code, secret, period, digits, algorithm })
Verification of email/phone number ownership
Here's the typical process for a one-time verification of a user's email/phone
number/etc.:
import { generateTOTP, verifyTOTP } from '@epic-web/totp'
const { otp, secret, digits, period, algorithm } = generateTOTP({
algorithm: 'SHA256',
period: 10 * 60,
})
await sendOtpToUser({
email: user.email,
otp,
secret,
digits,
period,
algorithm,
})
await saveVerificationToDatabase({
secret,
digits,
period,
algorithm,
target: user.email,
})
const code = await getCodeFromUser()
const userCodeConfig = await getVerificationFromDatabase({
target: user.email,
})
const isValid = verifyTOTP({ otp: code, ...userCodeConfig })
if (isValid) {
await deleteVerificationFromDatabase({ target: user.email })
} else {
}
Customizable Character Set for Increased Security
Why Charset Matters
When it comes to security, every bit of entropy counts. Entropy measures the
unpredictability and in turn the security of your OTPs. The traditional TOTP
setup often employs a 6-digit numerical code, providing a million (10^6)
combinations. This is the default behaviour for this implementation. While that
is robust, there's room for improvement.
By introducing a customizable character set feature, you can exponentially
increase the entropy of the OTPs, making them much more secure against
brute-force attacks. For example, if you extend your character set to include 26
uppercase letters and 10 digits, a 6-character OTP would have 36^6 = 2.1 billion
combinations. When paired with rate-limiting mechanisms, this configuration
becomes practically impervious to brute-force attacks.
Potential for Main Form of Authentication
With this added complexity, TOTPs can, in theory, be used as the primary form of
authentication, rather than just a second factor. This is particularly useful
for applications requiring heightened security.
Usage with Custom Character Set
In addition to the existing options, you can specify a charSet in both
generateTOTP
and verifyTOTP
.
Here's how you can generate an OTP with a custom character set:
import { generateTOTP, verifyTOTP } from '@epic-web/totp'
const { otp, secret, period, digits, algorithm, charSet } = generateTOTP({
charSet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
})
const isValid = verifyTOTP({
otp,
secret,
period,
digits,
algorithm,
charSet,
})
Just as an aside, you probably want to exclude the letter O and the number 0 to
make it easier for users to enter the code.
API
This library is built with jsdoc
, so hopefully your editor supports that and
will show you all this stuff, but just in case, here's that:
generateTOTP
verifyTOTP
getTOTPAuthUri
License
MIT