@merzin/otp
v0.4.1
Published
One-Time Password (OTP) generator and validator with support for algorithms SHA1, SHA256, SHA512 and MD5.
Maintainers
Readme
This is a utility to generate OTP (One-Time Password) codes. It can be used for two-factor authentication (2FA) or any other purpose where a one-time password is needed.
- HOTP (HMAC-Based One-Time Password Algorithm): RFC 4226
- TOTP (Time-Based One-Time Password Algorithm): RFC 6238
Features
- Generate HOTP and TOTP codes.
- Validate HOTP and TOTP codes.
- Generate and parse OTP URIs.
- Generate and parse OTP migration URIs.
- Generate Base32 encoded secret.
- Support for SHA1, SHA256 (SHA2), SHA512 (SHA2) and MD5 algorithms.
- Support for custom code length and time step.
- Support for Base32 encoded secret with padding or without padding.
Note: When implementing 2FA in your service, it is recommended to use TOTP with default options: secret with length of 20 bytes (32 characters of Base32), algorithm SHA1, digits 6 and period 30, as not all authenticator apps support other algorithms and options.
Note: When implementing an authenticator client, it is recommended to support both HOTP and TOTP and all algorithms and options to maximize compatibility with other services.
Usage
Library index @merzin/otp exports two (2) functions hotp and totp to
generate HOTP and TOTP codes respectively. Rest of the package is divided into
submodules for better bundle size and tree-shaking.
@merzin/otphotpfunctiontotpfunction
@merzin/otp/typesOtpTypetypeOtpAlgorithmtypeOtpParametersinterface
@merzin/otp/generategenerateSecretfunction
@merzin/otp/validatevalidateHotpfunctionvalidateTotpfunction
@merzin/otp/uriencodeOtpUrifunctionparseOtpUrifunction
@merzin/otp/migrationencodeOtpMigrationUrifunctionparseOtpMigrationUrifunction
Generating HOTP code
Function hotp takes one (1) argument which is an object with the following
properties:
secret: Base32 encoded secret (string)algorithm: Algorithm to use (string?). Default is SHA1.digits: Code length (number?). Default is 6.counter: Counter (number?). Default is 0.
import { hotp } from "@merzin/otp";
const code: string = hotp({
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
algorithm: "SHA1",
digits: 6,
counter: 0,
});Generating TOTP code
Function totp takes two (2) arguments. The first argument is an object with
the following properties:
secret: Base32 encoded secret (string)algorithm: Algorithm to use (string?). Default is SHA1.digits: Code length (number?). Default is 6.period: Time step in seconds (number?). Default is 30.
The second argument is the time to use in milliseconds from epoch (number). Default is current time.
import { totp } from "@merzin/otp";
const code: string = totp(
{
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
algorithm: "SHA1",
digits: 6,
period: 30,
},
Date.now(),
);Note:
totpis a wrapper aroundhotpand uses the current time to calculate the countercounter = Math.floor(Date.now() / 1000 / period).To calculate the next code, simply add the period to the current time
Date.now() + period * 1000. Similarly, to calculate the previous code, subtract the period from the current timeDate.now() - period * 1000. This is useful for verifying the code with a time window larger than given period.
Generate Secret
Function generateSecret takes two (2) optional arguments. The first argument
is the length of the secret in bytes (number?). Default is 20 bytes (32
characters in Base32). The second argument is whether to pad the secret with "="
(boolean?). Default is false. The function returns a Base32 encoded secret.
import { generateSecret } from "@merzin/otp/generate";
const secret: string = generateSecret(20, false);Validate HOTP
Function validateHotp takes three (3) arguments. The first argument is the
HOTP parameters object (the same as in hotp function). The second argument is
the code to validate (string). The third argument is an optional options object
with the following properties:
acceptPrevious: Accept previous code(s) (boolean|number?). Default isfalse.acceptNext: Accept next code(s) (boolean|number?). Default istrue.
Function returns either null if the code is invalid or the updated counter
(number) if the code is valid.
import { validateHotp } from "@merzin/otp/validate";
const updatedCounter: number | null = validateHotp(
{
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
algorithm: "SHA1",
digits: 6,
counter: 0,
},
"022694",
);Validate TOTP
Function validateTotp takes three (3) arguments. The first argument is the
TOTP parameters object (the same as in totp function). The second argument is
the code to validate (string). The third argument is an optional options object
with the following properties:
now: Time to use in milliseconds from epoch (number?). Default is current time.acceptPrevious: Accept previous code(s) (boolean|number?). Default istrue.acceptNext: Accept next code(s) (boolean|number?). Default istrue.
Function returns a boolean indicating whether the code is valid or not.
import { validateTotp } from "@merzin/otp/validate";
const valid: boolean = validateTotp(
{
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
algorithm: "SHA1",
digits: 6,
period: 30,
},
"605949",
);OTP Parameters
Interface OtpParameters is used for encoding and decoding OTP parameters. It
extends the hotp and totp objects. It has the following properties:
type: Type of OTP ("HOTP"|"TOTP").secret: Base32 encoded secret (string).name: Name of the OTP (string?).issuer: Issuer of the OTP (string?).algorithm: Algorithm to use ("SHA1"|"SHA256"|"SHA512"|"MD5"?).digits: Code length (number?).counter: Counter (number?). Should be used only for HOTP.period: Time step in seconds (number?). Should be used only for TOTP.
Encode OTP URI
Function encodeOtpUri takes two (2) arguments. The first argument is
OtpParameters object. The second argument is an optional
boolean indicating whether to include the default options in the URI which by
default is false. The function returns the OTP URI (string).
import { encodeOtpUri } from "@merzin/otp/uri";
const hotpUri: string = encodeOtpUri({
type: "HOTP",
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
name: "[email protected]",
issuer: "example.com",
algorithm: "SHA1",
digits: 6,
counter: 0,
});
const totpUri: string = encodeOtpUri({
type: "TOTP",
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
name: "[email protected]",
issuer: "example.com",
algorithm: "SHA1",
digits: 6,
period: 30,
});Parse OTP URI
Function parseOtpUri takes one (1) argument which is the OTP URI (string).
The function returns OtpParameters object. The function
throws an error in the following cases:
- URI protocol is not
otpauth. - Type is not
HOTPorTOTP(case insensitive). - Secret is not present.
- Algorithm is not "SHA1", "SHA256", "SHA512" or "MD5" (case insensitive, ignores dashes).
import { parseOtpUri } from "@merzin/otp/uri";
const uri =
"otpauth://hotp/example.com%3Auser%40mail.tld?secret=QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA&issuer=example.com&algorithm=SHA1&digits=6&counter=0";
const otpParameters: OtpParameters = parseOtpUri(uri);Encode OTP Migration URI
Function encodeOtpMigrationUri takes two (2) arguments. The first argument is
an array of OtpParameters objects. The second argument is
an optional options object with the following properties:
autoFillDefaults: Whether to auto-fill default values (boolean?). Default istrue.batchId: Batch ID (number?). Default is current time in milliseconds from epoch.entriesPerUri: Number of entries per URI (number?). Default is 10.pad: Whether to pad the data with "=" (boolean?). Default isfalse.
Function returns the OTP migration URI (string).
Note: TOTP entries with period other than 30 seconds are ignored.
import { encodeOtpMigrationUri } from "@merzin/otp/migration";
const uris: string[] = encodeOtpMigrationUri([
{
name: "[email protected]",
issuer: "example.com",
secret: "QRRQ4G3ZW6UY6EFODRKFLMO24POUFNKA",
type: "HOTP",
algorithm: "SHA1",
digits: 6,
counter: 4,
},
]);Parse OTP Migration URI
Function parseOtpMigrationUri takes one (1) argument which is the OTP
migration URI (string). The function returns an array of
OtpParameters objects. The function throws an error in the
following cases:
- URI protocol is not
otpauth-migration. - Data is not present.
import { parseOtpMigrationUri } from "@merzin/otp/migration";
const migrationUri =
"otpauth-migration://offline?data=Ck8KFIRjDht5t6mPEK4cVFWx2uPdQrVAEg11c2VyQG1haWwudGxkGgtleGFtcGxlLmNvbSABKAEwATgEQhM3YWI4ZTUxNzQ1MDY0MzcyOTEwEAIYASAA";
const otpParametersList: OtpParameters[] = parseOtpMigrationUri(migrationUri);Dependencies
- rfc4648 (license MIT): Base32 decoding and encoding the secret and Base64 encoding and decoding the OTP migration URI data.
- @noble/hashes (license MIT): HMAC and hashing algorithms SHA1, SHA256, SHA512 and MD5.
- protobufjs (license BSD-3-Clause): Encoding and decoding the OTP parameters to and from migration URIs.
Changelog
All notable changes to this project will be documented in CHANGELOG.md.
License
MIT license. See LICENSE for details.
