Speakeasy
Two-factor authentication for Node.js. Generate One-time passcode generator (HOTP/TOTP) with support for Google Authenticator.
Install / Use
/learn @Levminer/SpeakeasyREADME
@levminer/speakeasy
- This is still functional, but no longer maintained.
- This is a fork of the original speakeasy. I'm just modernized and fixed some stuff:
-
Use Buffer.alloc() and Buffer.from() instead deprecated new Buffer
-
ES6 syntax: let, const, arrow functions
-
Fixed some known bugs
-
Import syntax
Are you looking for a desktop cross platform two-factor authentication app? Check out: Authme
Jump to — Install · General Usage · Documentation · Contributing · License
Speakeasy is a one-time passcode generator, ideal for use in two-factor authentication, that supports Google Authenticator and other two-factor devices.
It is well-tested and includes robust support for custom token lengths, authentication windows, hash algorithms like SHA256 and SHA512, and other features, and includes helpers like a secret key generator.
Speakeasy implements one-time passcode generators as standardized by the [Initiative for Open Authentication (OATH)][oath]. The HMAC-Based One-Time Password (HOTP) algorithm defined by [RFC 4226][rfc4226] and the Time-Based One-time Password (TOTP) algorithm defined in [RFC 6238][rfc6238] are supported. This project incorporates code from [passcode][], originally a fork of Speakeasy, and [notp][].
<a name="install"></a>
Install
npm i @levminer/speakeasy
<a name="general-usage"></a>
General Usage
//require
const speakeasy = require("@levminer/speakeasy")
//import
import speakeasy from "@levminer/speakeasy"
Generating a key
// Generate a secret key.
let secret = speakeasy.generateSecret({ length: 20 })
// Access using secret.ascii, secret.hex, or secret.base32.
Getting a time-based token for the current time
// Generate a time-based token based on the base-32 key.
// HOTP (counter-based tokens) can also be used if `totp` is replaced by
// `hotp` (i.e. speakeasy.hotp()) and a `counter` is given in the options.
let token = speakeasy.totp({
secret: secret.base32,
encoding: "base32",
})
// Returns token for the secret at the current time
// Compare this to user input
Verifying a token
// Verify a given token
let tokenValidates = speakeasy.totp.verify({
secret: secret.base32,
encoding: "base32",
token: "123456",
window: 6,
})
// Returns true if the token matches
Verifying a token and calculating a delta
A TOTP is incremented every step time-step seconds. By default, the time-step
is 30 seconds. You may change the time-step using the step option, with units
in seconds.
// Verify a given token is within 3 time-steps (+/- 2 minutes) from the server
// time-step.
let tokenDelta = speakeasy.totp.verifyDelta({
secret: secret.base32,
encoding: "base32",
token: "123456",
window: 2,
step: 60,
})
// Returns {delta: 0} where the delta is the time step difference
// between the given token and the current time
Getting a time-based token for a custom time
let token = speakeasy.totp({
secret: secret.base32,
encoding: "base32",
time: 1453667708, // specified in seconds
})
// Verify a time-based token for a custom time
let tokenValidates = speakeasy.totp.verify({
secret: secret.base32,
encoding: "base32",
token: token,
time: 1453667708,
})
Calculating a counter-based token
// Get a counter-based token
let token = speakeasy.hotp({
secret: secret.base32,
encoding: "base32",
counter: 123,
})
// Verify a counter-based token
let tokenValidates = speakeasy.hotp.verify({
secret: secret.base32,
encoding: "base32",
token: "123456",
counter: 123,
})
Using other encodings
The default encoding (when encoding is not specified) is ascii.
// Specifying an ASCII token for TOTP
// (encoding is 'ascii' by default)
let token = speakeasy.totp({
secret: secret.ascii,
})
// Specifying a hex token for TOTP
let token = speakeasy.totp({
secret: secret.hex,
encoding: "hex",
})
Using other hash algorithms
The default hash algorithm is SHA1.
// Specifying SHA256
let token = speakeasy.totp({
secret: secret.ascii,
algorithm: "sha256",
})
// Specifying SHA512
let token = speakeasy.totp({
secret: secret.ascii,
algorithm: "sha512",
})
Getting an otpauth:// URL and QR code for non-SHA1 hash algorithms
// Generate a secret, if needed
let secret = speakeasy.generateSecret()
// By default, generateSecret() returns an otpauth_url for SHA1
// Use otpauthURL() to get a custom authentication URL for SHA512
let url = speakeasy.otpauthURL({ secret: secret.ascii, label: "Name of Secret", algorithm: "sha512" })
// Pass URL into a QR code generator
Specifying a window for verifying HOTP and TOTP
Verify a HOTP token with counter value 42 and a window of 10. HOTP has a one-sided window, so this will check counter values from 42 to 52, inclusive, and return a { delta: n } where n is the difference between the given counter value and the counter position at which the token was found, or undefined if it was not found within the window. See the <a href="#totp․verifyDelta">hotp․verifyDelta(options)</a> documentation for more info.
let token = speakeasy.hotp.verifyDelta({
secret: secret.ascii,
counter: 42,
token: "123456",
window: 10,
})
How this works:
// Set ASCII secret
let secret = "rNONHRni6BAk7y2TiKrv"
// Get HOTP counter token at counter = 42
let counter42 = speakeasy.hotp({ secret: secret, counter: 42 })
// => '566646'
// Get HOTP counter token at counter = 45
let counter45 = speakeasy.hotp({ secret: secret, counter: 45 })
// => '323238'
// Verify the secret at counter 42 with the actual value and a window of 10
// This will check all counter values from 42 to 52, inclusive
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter42, window: 10 })
// => { delta: 0 } because the given token at counter 42 is 0 steps away from the given counter 42
// Verify the secret at counter 45, but give a counter of 42 and a window of 10
// This will check all counter values from 42 to 52, inclusive
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter45, window: 10 })
// => { delta: 3 } because the given token at counter 45 is 0 steps away from given counter 42
// Not in window: specify a window of 1, which only tests counters 42 and 43, not 45
speakeasy.hotp.verifyDelta({ secret: secret, counter: 42, token: counter45, window: 1 })
// => undefined
// Shortcut to use verify() to simply return whether it is verified as within the window
speakeasy.hotp.verify({ secret: secret, counter: 42, token: counter45, window: 10 })
// => true
// Not in window: specify a window of 1, which only tests counters 42 and 43, not 45
speakeasy.hotp.verify({ secret: secret, counter: 42, token: counter45, window: 1 })
// => false
Verify a TOTP token at the current time with a window of 2. Since the default time step is 30 seconds, and TOTP has a two-sided window, this will check tokens between [current time minus two tokens before] and [current time plus two tokens after]. In other words, with a time step of 30 seconds, it will check the token at the current time, plus the tokens at the current time minus 30 seconds, minus 60 seconds, plus 30 seconds, and plus 60 seconds – basically, it will check tokens between a minute ago and a minute from now. It will return a { delta: n } where n is the difference between the current time step and the counter position at which the token was found, or undefined if it was not found within the window. See the <a href="#totp․verifyDelta">totp․verifyDelta(options)</a> documentation for more info.
let verified = speakeasy.totp.verifyDelta({
secret: secret.ascii,
token: "123456",
window: 2,
})
The mechanics of TOTP windows are the same as for HOTP, as shown above, just with two-sided windows, meaning that the delta value can be negative if the token is found before the given time or counter.
let secret = "rNONHRni6BAk7y2TiKrv"
// By way of example, we will force TOTP to return tokens at time 1453853945 and
// at time 1453854005 (60 seconds ahead, or 2 steps ahead)
let token1 = speakeasy.totp({ secret: secret, time: 1453853945 }) // 625175
let token3 = speakeasy.totp({ secret: secret, time: 1453854005 }) // 222636
let token2 = speakeasy.totp({ secret: secret, time: 1453854065 }) // 013052
// We can check the time at token 3, 1453853975, with token 1, but use a window of 2
// With a time step of 30 seconds, this will check all tokens from 60 seconds
// before the time to 60 seconds after the time
speakeasy.totp.verifyDelta({ secret: secret, token: token1, window: 2, time: 1453854005 })
// => { delta: -2 }
// token is valid because because token is 60 seconds before time
speakeasy.totp.verify({ secret: secret, token: token1, window: 2, time: 1453854005 })
// => true
// token is valid because because token is 0 seconds before time
speakeasy.totp.verify({ secret: secret, token: token3, window: 2, time: 1453854005 })
// => true
// token is valid because because token is 60 seconds after time
speakeasy.totp.verify({ secret: secret, token: token2, window: 2, time: 1453854005 })
// => true
// This signifies that the given token, token1, is -2 steps away from
// the given time, which means that it is the token for the value at
// (-2 * time step) = (-2 * 30 seconds) = 60 seconds ago.
As shown previously, you can also change verifyDelta() to verify() to simply return a boolean if the given token is within the given window.
<a name="documentation"></a>
