@shworx/otpkit
v0.1.3
Published
A TypeScript toolkit for OTP credential import, export, and interoperability.
Maintainers
Readme
OTPKit
OTPKit is a TypeScript/JavaScript toolkit for working with One-Time Password (OTP) credentials.
It provides a common representation for OTP accounts and codecs for importing and exporting credentials between different OTP formats.
Currently supported formats include:
- OTPAuth URIs
- Google Authenticator migration payloads
Features
- TOTP support
- HOTP support
- OTPAuth URI parsing and generation
- Google Authenticator migration payload parsing
- Google Authenticator migration payload generation
- Support for multiple Google Authenticator migration batches
- RFC 4648 Base32 encoding and decoding
- SHA-1, SHA-256 and SHA-512 algorithms
- 6, 7 and 8 digit OTP configurations
- Strong TypeScript typing
- Format-independent
OTPAccountrepresentation - High-level
OTPKitAPI - Direct codec access for advanced use cases
- ES module based
Supported OTP Configuration
OTPKit represents an OTP credential using a common OTPAccount structure.
interface OTPAccount {
readonly label: string;
readonly issuer?: string;
readonly secret: string;
readonly otp: OTPOptions;
}OTP options contain the OTP type and algorithm as well as the required parameters for TOTP or HOTP.
TOTP
const account: OTPAccount = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};HOTP
const account: OTPAccount = {
label: "Administrator",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.HOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
counter: 0
}
};Installation
OTPKit can be installed from npm:
npm install otpkitAfter installation, import the public API from the package:
import { OTPKit } from "otpkit";OTPKit can also be used directly from a cloned repository when developing the library itself:
git clone https://github.com/shworx/otpkit.git
cd otpkit
npm install
npm run buildThe compiled library and TypeScript declaration files are generated in the dist/ directory.
High-Level API
For most applications, the recommended entry point is OTPKit.
OTPKit provides a format-independent interface for encoding and decoding OTP credentials without requiring application code to instantiate individual codecs.
import { OTPKit } from "otpkit";Decode an OTP Credential
OTPKit.decode() automatically determines the supported format and returns the corresponding OTP accounts.
import { OTPKit } from "otpkit";
const input =
"otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example";
const accounts = OTPKit.decode(input);
console.log(accounts);The same method can be used with a Google Authenticator migration URI:
import { OTPKit } from "otpkit";
const input =
"otpauth-migration://offline?data=...";
const accounts = OTPKit.decode(input);
for (const account of accounts) {
console.log(account.label);
console.log(account.issuer);
}The application therefore does not need to know whether the input is an OTPAuth URI or a Google migration URI.
OTPKit.decode() also accepts percent-encoded migration URIs, which is useful when the complete URI has been obtained from another URL-encoded source.
Note: Check
examples/google-export-qrcode.htmlfor a full working example of reading and decoding a Google Authenticator Export QR code.
Encode an OTP Credential
OTPKit.encode() can encode OTP accounts into a specified format.
import {
OTPAlgorithm,
OTPFormat,
OTPKit,
OTPType
} from "otpkit";
const account = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};
const uri = OTPKit.encode(
[account],
OTPFormat.OTP_AUTH
);
console.log(uri);For formats that produce a single string, the result can be used directly.
Google Authenticator migration data may consist of multiple batches. For this reason, migration exports are represented separately when batch information is required.
Recommended Usage
For applications that primarily need OTP interoperability, the high-level API is generally the preferred approach:
import {
OTPAlgorithm,
OTPFormat,
OTPKit,
OTPType
} from "otpkit";
const account = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};
const otpAuthUri = OTPKit.encode(
[account],
OTPFormat.OTP_AUTH
);
const importedAccounts = OTPKit.decode(otpAuthUri);
console.log(importedAccounts);Use the direct codecs when the application needs format-specific functionality or more control over the individual format.
JavaScript Usage
OTPKit is an ES module.
After installing the package:
import { OTPKit } from "otpkit";For example:
import { OTPKit } from "otpkit";
const accounts = OTPKit.decode(
"otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example"
);
console.log(accounts);The individual codecs can also be imported directly:
import {
GoogleMigrationCodec,
OTPAuthCodec
} from "otpkit";TypeScript Usage
OTPKit provides TypeScript declarations as part of the package.
Import the public API directly from the package:
import {
OTPAccount,
OTPAlgorithm,
OTPFormat,
OTPKit,
OTPType
} from "otpkit";Create an OTP Account
const account: OTPAccount = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};Encode and Decode
const encoded = OTPKit.encode(
[account],
OTPFormat.OTP_AUTH
);
const decoded = OTPKit.decode(encoded);
console.log(decoded[0]);Direct Codec Usage
OTPKit also exposes its codecs directly.
Direct codec access is useful when an application already knows which format it is working with or needs format-specific functionality.
The currently available codecs are:
OTPAuthCodecGoogleMigrationCodec
OTPAuth Codec
OTPAuthCodec handles the standard otpauth:// URI format.
It supports both TOTP and HOTP credentials.
Decode
import { OTPAuthCodec } from "otpkit";
const codec = new OTPAuthCodec();
const accounts = codec.decode(
"otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example"
);
const account = accounts[0]!;
console.log(account.label);
console.log(account.issuer);
console.log(account.secret);Encode
const uri = codec.encode(account);
console.log(uri);Google Authenticator Migration Codec
GoogleMigrationCodec handles Google Authenticator's migration format.
A migration payload is transported using the following URI scheme:
otpauth-migration://offline?data=<payload>The payload contains a protobuf-encoded collection of OTP credentials.
Decode a Migration URI
import { GoogleMigrationCodec } from "otpkit";
const codec = new GoogleMigrationCodec();
const accounts = codec.decode(
"otpauth-migration://offline?data=..."
);
for (const account of accounts) {
console.log(account.label);
console.log(account.issuer);
console.log(account.secret);
}The codec converts Google's migration representation into OTPKit's common OTPAccount representation.
Check Whether a Value Is Supported
Codecs expose a supports() method.
const codec = new GoogleMigrationCodec();
if (codec.supports(input)) {
const accounts = codec.decode(input);
console.log(accounts);
}Invalid or unsupported migration data returns false from supports().
Google Migration Export
Google Authenticator migration data can also be generated from normal OTPAccount objects.
Export a Single Migration URI
import {
GoogleMigrationCodec,
OTPAlgorithm,
OTPType
} from "otpkit";
const codec = new GoogleMigrationCodec();
const account = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};
const migrationUri = codec.encode([account]);
console.log(migrationUri);The returned value is a complete migration URI:
otpauth-migration://offline?data=...It can therefore be passed directly to a QR-code generator.
Multiple Migration Batches
Google Authenticator migration payloads have a maximum payload size.
When multiple accounts cannot fit into a single migration payload, OTPKit splits them into multiple batches.
For this use case, use encodeMigration().
const migrations = codec.encodeMigration(accounts);
for (const migration of migrations) {
console.log(migration.batchIndex);
console.log(migration.batchSize);
console.log(migration.batchId);
console.log(migration.data);
}Each result is a MigrationExport:
interface MigrationExport {
readonly data: string;
readonly batchIndex: number;
readonly batchSize: number;
readonly batchId: number;
}data contains the Base64-encoded protobuf payload.
The migration URI can be constructed from the exported data:
const uris = migrations.map(
migration =>
`otpauth-migration://offline?data=${migration.data}`
);Each URI represents one migration batch.
The batchIndex, batchSize and batchId values allow the batches to be associated with the same migration operation.
Encode → Decode Round Trip
OTPKit can convert an account into a Google migration URI and recover the same account again.
Using the high-level API:
import {
OTPAlgorithm,
OTPFormat,
OTPKit,
OTPType
} from "otpkit";
const original = {
label: "[email protected]",
issuer: "Example",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}
};
const migrationUri = OTPKit.encode(
[original],
OTPFormat.GOOGLE_MIGRATION
);
const accounts = OTPKit.decode(migrationUri);
console.log(accounts[0]);This makes OTPKit useful for applications that need to move OTP credentials between different applications or storage formats.
Base32
OTPKit includes an RFC 4648 Base32 implementation.
import { Base32 } from "otpkit";Encode
const encoded = Base32.encode(
new Uint8Array([72, 101, 108, 108, 111])
);
console.log(encoded);The encoder returns uppercase Base32 without padding.
Decode
const decoded = Base32.decode("JBSWY3DPEHPK3PXP");
console.log(decoded);The decoder accepts:
- uppercase characters
- lowercase characters
- whitespace
- hyphen separators
- optional Base32 padding
OTP Algorithms
OTPKit currently supports:
OTPAlgorithm.SHA1
OTPAlgorithm.SHA256
OTPAlgorithm.SHA512Example:
const account = {
label: "[email protected]",
secret: "JBSWY3DPEHPK3PXP",
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA256,
digits: 8,
period: 30
}
};OTP Types
Two OTP types are currently supported:
OTPType.TOTP
OTPType.HOTPTOTP accounts use a time period:
otp: {
type: OTPType.TOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
period: 30
}HOTP accounts use a counter:
otp: {
type: OTPType.HOTP,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
counter: 0
}Validation
OTPKit validates OTP accounts before they are exported.
Validation includes:
- non-empty labels
- valid Base32 secrets
- supported OTP types
- supported algorithms
- valid digit counts
- valid TOTP periods
- required HOTP counters
- prohibition of TOTP counters
- prohibition of HOTP periods
Invalid accounts result in an appropriate OTPKit error.
Error Handling
OTPKit provides dedicated error classes for common failure conditions.
Examples include:
InvalidBase32Error
InvalidOTPAccountError
InvalidOTPOptionsError
InvalidOTPURIError
OTPKitError
UnsupportedFormatErrorApplications can catch specific errors when required:
try {
const accounts = OTPKit.decode(input);
} catch (error) {
if (error instanceof InvalidOTPURIError) {
console.error("Invalid OTPAuth URI.");
}
}Public API
The package entry point exposes the public API:
import {
OTPKit,
OTPAccount,
OTPAlgorithm,
OTPFormat,
OTPOptions,
OTPType,
OTPAuthCodec,
GoogleMigrationCodec,
Base32
} from "otpkit";Migration-related public types include:
MigrationExport
MigrationOTPParameter
MigrationPayloadThe high-level OTPKit API is intended for normal application usage.
Individual codecs are exposed for applications that need direct format-specific control.
Internal implementation classes such as migration parsers, migration writers and protobuf readers/writers are not required for normal library usage.
Building
When working from the source repository, build the library with:
npm run buildThe generated files are placed in:
dist/The build includes JavaScript modules and TypeScript declaration files.
Type Checking
Run TypeScript type checking without generating output:
npm run typecheckTesting
OTPKit uses Vitest for its test suite.
Run all tests:
npm testThe test suite covers:
- binary readers and writers
- protobuf encoding and decoding
- Base32 encoding and decoding
- OTPAuth parsing and generation
- Google migration parsing and generation
- migration batching
- OTP validation
- error handling
- public API behavior
Development
Clone the repository:
git clone https://github.com/shworx/otpkit.git
cd otpkitInstall dependencies:
npm installRun the test suite:
npm testRun type checking:
npm run typecheckBuild the library:
npm run buildProject Structure
The project is organized into functional modules:
src/
├── binary/
│ ├── BinaryReader.ts
│ ├── BinaryWriter.ts
│ └── Endianness.ts
├── codecs/
│ ├── Codec.ts
│ ├── GoogleMigrationCodec.ts
│ └── OTPAuthCodec.ts
├── core/
│ ├── OTPAccount.ts
│ ├── OTPAlgorithm.ts
│ ├── OTPFormat.ts
│ ├── OTPOptions.ts
│ └── OTPType.ts
├── errors/
├── migration/
│ ├── MigrationExport.ts
│ ├── MigrationOTPParameter.ts
│ ├── MigrationPayload.ts
│ ├── MigrationPayloadParser.ts
│ ├── MigrationPayloadWriter.ts
│ └── MigrationExporter.ts
├── protobuf/
├── utils/
│ └── Base32.ts
├── validation/
├── OTPKit.ts
└── index.tsThe architecture separates the common OTP model from individual credential formats.
This allows additional codecs to be added without changing the application's internal representation of an OTP account.
Design Goals
OTPKit is intended to provide a format-independent foundation for OTP credential interoperability.
The core concept is:
┌─────────────────┐
│ OTPAccount │
└────────┬────────┘
│
┌────────▼────────┐
│ OTPKit │
└────────┬────────┘
│
┌─────────────┴─────────────┐
│ │
┌──────▼──────┐ ┌──────▼────────────┐
│ OTPAuthCodec│ │GoogleMigrationCodec│
└──────┬──────┘ └──────┬────────────┘
│ │
otpauth:// otpauth-migration://Applications can work with OTPAccount rather than directly with individual serialization formats.
This makes it possible to import an account from one format and export it into another format without coupling application code to either format.
For applications requiring format-specific functionality, the codecs remain directly accessible.
Current Limitations
OTPKit currently has the following limitations:
- Google migration payloads may require multiple QR codes when many accounts are exported.
- OTPKit handles credential representation and serialization; it does not generate OTP codes itself.
License
See LICENSE.md for license information.
Author
OTPKit is developed by SHWorX (Steffen Haase).
