From 07f40f6e8825fd9e694f56116b091dee069d6d19 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 24 Jun 2023 13:50:47 +1000 Subject: [PATCH 1/3] feat: document TOTP adapter --- _includes/parse-server/third-party-auth.md | 179 ++++++++++++++++++++- 1 file changed, 173 insertions(+), 6 deletions(-) diff --git a/_includes/parse-server/third-party-auth.md b/_includes/parse-server/third-party-auth.md index 73415d5c..733c0be7 100644 --- a/_includes/parse-server/third-party-auth.md +++ b/_includes/parse-server/third-party-auth.md @@ -61,6 +61,7 @@ Note, most of them don't require a server configuration so you can use them dire ``` The options passed to Parse Server: + ```js { auth: { @@ -88,6 +89,7 @@ Learn more about [Facebook login](https://developers.facebook.com/docs/authentic ``` The options passed to Parse Server: + ```js { auth: { @@ -202,7 +204,7 @@ The options passed to Parse Server: { auth: { keycloak: { - config: require(`./auth/keycloak.json`) // Required + config: require(`./auth/keycloak.json`); // Required } } } @@ -305,7 +307,7 @@ As of Parse Server 3.7.0 you can use [PhantAuth](https://www.phantauth.net/). { "phantauth": { "id": "user's PhantAuth sub (string)", - "access_token": "an authorized PhantAuth access token for the user", + "access_token": "an authorized PhantAuth access token for the user" } } ``` @@ -401,7 +403,172 @@ On this module, you need to implement and export those two functions `validateAu For more information about custom auth please see the examples: -- [Facebook OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/facebook.js) -- [Twitter OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/twitter.js) -- [Instagram OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/instagram.js) -- [Microsoft Graph OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/microsoft.js) +* [Facebook OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/facebook.js) +* [Twitter OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/twitter.js) +* [Instagram OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/instagram.js) +* [Microsoft Graph OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/microsoft.js) + +## Multi-factor authentication + +### Time-based One-Time passwords (TOTP) + +Time-based one-time passwords are considered best practise for multi-factor authentication, as SMS OTPs can be intercepted via [SS7](https://www.theguardian.com/technology/2016/apr/19/ss7-hack-explained-mobile-phone-vulnerability-snooping-texts-calls) or [sim swap](https://us.norton.com/blog/mobile/sim-swap-fraud) attacks. TOTP authentication can be configured using: + +```js +{ + auth: { + mfa: { + enabled: true, + options: ['TOTP'], + algorithm: 'SHA1', + digits: 6, + period: 30, + }, + }, +} +``` + +To enable MFA for a user, the [OTPAuth](https://github.com/hectorm/otpauth) package can be used. + +First, create an TOTP object: + +```js +const secret = new OTPAuth.Secret(); +const totp = new OTPAuth.TOTP({ + algorithm: "SHA1", + digits: 6, + period: 30, + secret, +}); +``` + +Next, ask the user to add the TOTP code to their authenticator app: + +```js +const uri = totp.toString(); +``` + +This URI can also be scanned as a QR code, using the [QRCode](https://www.npmjs.com/package/qrcode) package. + +```js +QRCode.toCanvas(document.getElementById("canvas"), uri); +``` + +Now, to confirm the user has correctly added the TOTP to their authenticator app, ask them to provide a valid code. + +```js +const token = ""; // user inputted code +await user.save({ + authData: { + mfa: { + secret: secret.base32, // secret is from the TOTP object above + token, // token is generated from the users authenticator app + }, + }, +}); +``` + +Now, MFA will be enabled for the user. You can access recovery keys by: + +```js +const recovery = user.get("authDataResponse"); +``` + +It's also recommended to clear the authData from the client side: + +```js +await user.fetch(); +``` + +Now, when this user logs in, the will need to provide a valid MFA code: + +```js +const login = async () => { + try { + await Parse.User.logIn(username, password); + } catch (e) { + if (e.message === 'Missing additional authData mfa') { + // show code input dialog here + } + } +} +const loginWithTOTP = async () => { + try { + await Parse.User.logInWithAdditionalAuth(username, password, { + mfa: // mfa code here + }); + } catch (e) { + // display error + } +} +``` + +### SMS One-Time passwords + +It is recommended to use TOTP MFA over SMS OTPs as SMS OTPs can be intercepted via [SS7](https://www.theguardian.com/technology/2016/apr/19/ss7-hack-explained-mobile-phone-vulnerability-snooping-texts-calls) or [sim swap](https://us.norton.com/blog/mobile/sim-swap-fraud) attacks. + +```js +{ + auth: { + mfa: { + enabled: true, + options: ['SMS'], + sendSMS(otp, mobileNumber) { + // Use an SMS service to send the SMS OTP + }, + digits: 6, + period: 30, + }, + }, +} +``` + +To enable SMS MFA for a user, first set the users' mobile number. + +```js +await user.save({ authData: { mfa: { mobile: "+11111111111" } } }); +``` + +Next, ask the user to confirm the SMS code they just received + +```js +await user.save({ authData: { mfa: { mobile: "+11111111111", token: code } } }); +``` + +Now, SMS MFA will be enabled for the user. You can access recovery keys by accessing: + +```js +const recovery = user.get("authDataResponse"); +``` + +It's also recommended to clear the authData from the client side: + +```js +await user.fetch(); +``` + +Now, when this user logs in, the will need to provide a valid MFA code: + +```js +const login = async () => { + try { + await Parse.User.logIn(username, password); + } catch (e) { + if (e.message === 'Missing additional authData mfa') { + // show code input dialog here + await Parse.User.logInWithAdditionalAuth(username, password, { + mfa: true // this triggers an SMS to be sent + }); + } + } +} +const loginWithTOTP = async () => { + try { + await Parse.User.logInWithAdditionalAuth(username, password, { + mfa: // mfa code here + }); + } catch (e) { + // display error + } +} +``` From 5822b0df35550e00d29f6ec80abbbc41022d9aca Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 24 Jun 2023 13:53:21 +1000 Subject: [PATCH 2/3] Update third-party-auth.md --- _includes/parse-server/third-party-auth.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_includes/parse-server/third-party-auth.md b/_includes/parse-server/third-party-auth.md index 733c0be7..0195940c 100644 --- a/_includes/parse-server/third-party-auth.md +++ b/_includes/parse-server/third-party-auth.md @@ -403,10 +403,10 @@ On this module, you need to implement and export those two functions `validateAu For more information about custom auth please see the examples: -* [Facebook OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/facebook.js) -* [Twitter OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/twitter.js) -* [Instagram OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/instagram.js) -* [Microsoft Graph OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/microsoft.js) +- [Facebook OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/facebook.js) +- [Twitter OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/twitter.js) +- [Instagram OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/instagram.js) +- [Microsoft Graph OAuth](https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/microsoft.js) ## Multi-factor authentication From 2968100e7bbe6108a1157c27704b28d4a0e6bdaf Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 24 Jul 2023 16:46:39 +1000 Subject: [PATCH 3/3] Update third-party-auth.md --- _includes/parse-server/third-party-auth.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/_includes/parse-server/third-party-auth.md b/_includes/parse-server/third-party-auth.md index 0195940c..b633be1b 100644 --- a/_includes/parse-server/third-party-auth.md +++ b/_includes/parse-server/third-party-auth.md @@ -474,7 +474,7 @@ Now, MFA will be enabled for the user. You can access recovery keys by: const recovery = user.get("authDataResponse"); ``` -It's also recommended to clear the authData from the client side: +It's also recommended to clear the authData from the client side, so that the recovery keys cannot be accessed by inspecing the console: ```js await user.fetch(); @@ -505,7 +505,7 @@ const loginWithTOTP = async () => { ### SMS One-Time passwords -It is recommended to use TOTP MFA over SMS OTPs as SMS OTPs can be intercepted via [SS7](https://www.theguardian.com/technology/2016/apr/19/ss7-hack-explained-mobile-phone-vulnerability-snooping-texts-calls) or [sim swap](https://us.norton.com/blog/mobile/sim-swap-fraud) attacks. +It is recommended to use TOTP MFA over SMS OTPs as SMS OTPs are sent in plain-text and can be intercepted by attacks at the client end, such as sim-swap attacks, or SS7 interception attacks. ```js { @@ -526,13 +526,13 @@ It is recommended to use TOTP MFA over SMS OTPs as SMS OTPs can be intercepted v To enable SMS MFA for a user, first set the users' mobile number. ```js -await user.save({ authData: { mfa: { mobile: "+11111111111" } } }); +await user.save({ authData: { mfa: { mobile: "+15555555555" } } }); ``` Next, ask the user to confirm the SMS code they just received ```js -await user.save({ authData: { mfa: { mobile: "+11111111111", token: code } } }); +await user.save({ authData: { mfa: { mobile: "+15555555555", token: code } } }); ``` Now, SMS MFA will be enabled for the user. You can access recovery keys by accessing: @@ -541,7 +541,7 @@ Now, SMS MFA will be enabled for the user. You can access recovery keys by acces const recovery = user.get("authDataResponse"); ``` -It's also recommended to clear the authData from the client side: +It's also recommended to clear the authData from the client side, so that the recovery keys cannot be accessed by inspecing the console: ```js await user.fetch(); @@ -557,7 +557,9 @@ const login = async () => { if (e.message === 'Missing additional authData mfa') { // show code input dialog here await Parse.User.logInWithAdditionalAuth(username, password, { - mfa: true // this triggers an SMS to be sent + mfa: { + token: 'request', + } // this triggers an SMS to be sent }); } } @@ -565,7 +567,9 @@ const login = async () => { const loginWithTOTP = async () => { try { await Parse.User.logInWithAdditionalAuth(username, password, { - mfa: // mfa code here + mfa: { + token: // mfa code here + } }); } catch (e) { // display error