AJ ONeal
9 years ago
4 changed files with 221 additions and 2 deletions
@ -1,2 +1,102 @@ |
|||
# node-authenticator |
|||
Two- / Multi- Factor Authenication (2FA / MFA) for node.js |
|||
Node.js Authenticator |
|||
===================== |
|||
|
|||
Two- and Multi- Factor Authenication (2FA / MFA) for node.js |
|||
|
|||
There are a number of apps that various websites use to give you 6-digit codes to increase security when you log in: |
|||
|
|||
* Authy [iPhone](https://itunes.apple.com/us/app/authy/id494168017?mt=8) [Android](https://play.google.com/store/apps/details?id=com.authy.authy&hl=en) [Chrome](https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=en) [Linux](https://www.authy.com/personal/) [OS X](https://www.authy.com/personal/) [BlackBerry](https://appworld.blackberry.com/webstore/content/38831914/?countrycode=US&lang=en) |
|||
* Google Authenticator [iPhone](https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8) [Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en) |
|||
* Microsoft Authenticator [Windows Phone](https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj) [Android](https://play.google.com/store/apps/details?id=com.microsoft.msa.authenticator) [iPhone]() |
|||
* GAuth [FxOS](https://marketplace.firefox.com/app/gauth/) |
|||
|
|||
There are many [Services that Support MFA](http://lifehacker.com/5938565/heres-everywhere-you-should-enable-two-factor-authentication-right-now), |
|||
including Google, Microsoft, Facebook, Digital Ocean, for starters. |
|||
|
|||
This module uses [`notp`](https://github.com/guyht/notp) which implements `TOTP` [(RFC 6238)](https://www.ietf.org/rfc/rfc6238.txt) |
|||
(the *Authenticator* standard), which is based on `HOTP` [(RFC 4226)](https://www.ietf.org/rfc/rfc4226.txt) |
|||
to provide codes that are exactly compatible with all other *Authenticator* apps and services that use them. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
```bash |
|||
npm install authenticator --save |
|||
``` |
|||
|
|||
```javascript |
|||
'use strict'; |
|||
|
|||
var authenticator = require('authenticator'); |
|||
|
|||
var formattedKey = authenticator.generateKey(); |
|||
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz" |
|||
|
|||
var formattedToken = authenticator.generateToken(formattedKey); |
|||
// "957 124" |
|||
|
|||
authenticator.verifyToken(formattedKey, formattedToken); |
|||
// { delta: 0 } |
|||
|
|||
authenticator.verifyToken(formattedKey, '000 000'); |
|||
// null |
|||
``` |
|||
|
|||
QRCode |
|||
------ |
|||
|
|||
See <https://davidshimjs.github.io/qrcodejs/> |
|||
|
|||
```javascript |
|||
'use strict'; |
|||
|
|||
var el = document.querySelector('.js-qrcode-canvas'); |
|||
var link = "otpauth://totp/{{NAME}}?secret={{KEY}}"; |
|||
var name = "Your Service"; |
|||
// remove spaces, hyphens, equals, whatever |
|||
var key = "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz".replace(/\W/g, '').toLowerCase(); |
|||
|
|||
var qr = new QRCode(el, { |
|||
text: link.replace(/{{NAME}}/g, name).replace(/{{KEY}}/g, key) |
|||
}); |
|||
``` |
|||
|
|||
Formatting |
|||
---------- |
|||
|
|||
All non-alphanumeric characters are ignored, so you could just as well use hyphens |
|||
or periods or whatever suites your use case. |
|||
|
|||
These are just as valid: |
|||
|
|||
* "acqo ua72 d3yf a4e5 - uorx ztkh j2xl 3wiz" |
|||
* "98.24.63" |
|||
|
|||
0, 1, 8, and 9 also not used (so that base32). |
|||
To further avoid confusion with O, o, L, l, I, B, and g |
|||
you may wish to display lowercase instead of uppercase. |
|||
|
|||
TODO: should this library replace 0 with o, 1 with l (or I?), 8 with b, 9 with g, and so on? |
|||
|
|||
90-second Window |
|||
---------------- |
|||
|
|||
The window is set to +/- 1, meaning each token is valid for a total of 90 seconds |
|||
(-30 seconds, +0 seconds, and +30 seconds) |
|||
to account for time drift (which should be very rare for mobile devices) |
|||
and humans who are handicapped or otherwise struggle with quick fine motor skills (like my grandma). |
|||
|
|||
|
|||
Why not SpeakEasy? |
|||
------------------ |
|||
|
|||
I took a look at the code and I didn't feel comfortable using it. |
|||
|
|||
For any module related to security I want to see that the code is clean, |
|||
well-maintained, and that any security-related bugs are addressed. |
|||
|
|||
The author was obviously not well-versed in JavaScript at the time |
|||
that he wrote it and it hasn't been cleaned up since. |
|||
Also, the author hasn't been responsive to issues and pull requests. |
|||
|
|||
The notp author has been responsive, but notp doesn't do everything I would like. |
|||
|
@ -0,0 +1,59 @@ |
|||
'use strict'; |
|||
|
|||
var crypto = require('crypto'); |
|||
var b32 = require('thirty-two'); |
|||
|
|||
// Generate a key
|
|||
function generateOtpKey() { |
|||
// 20 cryptographically random binary bytes (160-bit key)
|
|||
var key = crypto.randomBytes(20); |
|||
|
|||
return key; |
|||
} |
|||
|
|||
// Text-encode the key as base32 (in the style of Google Authenticator - same as Facebook, Microsoft, etc)
|
|||
function encodeGoogleAuthKey(bin) { |
|||
// 32 ascii characters without trailing '='s
|
|||
var base32 = b32.encode(bin).toString('utf8').replace(/=/g, ''); |
|||
|
|||
// lowercase with a space every 4 characters
|
|||
var key = base32.toLowerCase().replace(/(\w{4})/g, "$1 ").trim(); |
|||
|
|||
return key; |
|||
} |
|||
|
|||
function generateGoogleAuthKey() { |
|||
return encodeGoogleAuthKey(generateOtpKey()); |
|||
} |
|||
|
|||
// Binary-decode the key from base32 (Google Authenticator, FB, M$, etc)
|
|||
function decodeGoogleAuthKey(key) { |
|||
// decode base32 google auth key to binary
|
|||
var unformatted = key.replace(/\W+/g, '').toUpperCase(); |
|||
var bin = b32.decode(unformatted); |
|||
|
|||
return bin; |
|||
} |
|||
|
|||
// Generate a Google Auth Token
|
|||
function generateGoogleAuthToken(key) { |
|||
var bin = decodeGoogleAuthKey(key); |
|||
var notp = require('notp'); |
|||
|
|||
return notp.totp.gen(bin); |
|||
} |
|||
|
|||
// Verify a Google Auth Token
|
|||
function verifyGoogleAuthToken(key, token) { |
|||
var bin = decodeGoogleAuthKey(key); |
|||
var notp = require('notp'); |
|||
|
|||
token = token.replace(/\W+/g, ''); |
|||
|
|||
// window is +/- 1 period of 30 seconds
|
|||
return notp.totp.verify(token, bin, { window: 1, time: 30 }); |
|||
} |
|||
|
|||
module.exports.generateKey = generateGoogleAuthKey; |
|||
module.exports.generateToken = generateGoogleAuthToken; |
|||
module.exports.verifyToken = verifyGoogleAuthToken; |
@ -0,0 +1,24 @@ |
|||
'use strict'; |
|||
|
|||
var authenticator = require('./authenticator'); |
|||
|
|||
var formattedKey = authenticator.generateKey(); |
|||
console.log(formattedKey); |
|||
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz"
|
|||
|
|||
var formattedToken = authenticator.generateToken(formattedKey); |
|||
console.log(formattedToken); |
|||
// "957 124"
|
|||
|
|||
var result = authenticator.verifyToken(formattedKey, formattedToken); |
|||
console.log(result); |
|||
// { delta: 0 }
|
|||
|
|||
result = authenticator.verifyToken(formattedKey, '000-000'); |
|||
console.log(result); |
|||
// null
|
|||
|
|||
// result will allways be one of
|
|||
// (failure) null
|
|||
// (success) { delta: -1 }, { delta: 0 }, or { delta: 1 }
|
|||
// delta lets you know which way time drift is occurring
|
@ -0,0 +1,36 @@ |
|||
{ |
|||
"name": "authenticator", |
|||
"version": "1.0.0", |
|||
"description": "Two- / Multi- Factor Authenication (2FA / MFA) for node.js", |
|||
"main": "authenticator.js", |
|||
"scripts": { |
|||
"test": "node example.js" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/Daplie/node-authenticator.git" |
|||
}, |
|||
"keywords": [ |
|||
"authenticator", |
|||
"2fa", |
|||
"mfa", |
|||
"token", |
|||
"key", |
|||
"base32", |
|||
"code", |
|||
"generator", |
|||
"authy", |
|||
"google", |
|||
"microsoft" |
|||
], |
|||
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com/)", |
|||
"license": "Apache-2.0", |
|||
"bugs": { |
|||
"url": "https://github.com/Daplie/node-authenticator/issues" |
|||
}, |
|||
"homepage": "https://github.com/Daplie/node-authenticator#readme", |
|||
"dependencies": { |
|||
"notp": "^2.0.3", |
|||
"thirty-two": "0.0.2" |
|||
} |
|||
} |
Loading…
Reference in new issue