How are things going with 2FA

We’re in the process of researching 2FA, which is prefered to be done via build-in Parse Server functionality, than rolling our own solution.

There are a number of chats about this in GitHub issues (see resources below), with tasks and branches at various stages of completion, however, activity seems to stop in Sep 21.

I was wondering if I can use this thread to kick start the conversation around improving the AuthAdaptor and providing a recommended solution for the implementation of 2FA.

I look forward to hearing your thoughts and if we can get the PRs tested and merged to a release soon.

Resources

Two Factor Authentication

Improve AuthAdapter capabilities

Hi @Taylorsuk,

You are right 2FA system and auth challenge feature are missing on parse server.
I’ve worked a lot to improve the Auth Adapter interface to introduce 2FA and help developers to write their own AuthAdapters easily (validation, registration, challenge and many more)

The PR is fully ready here and should be shipped on Parse v5: https://github.com/parse-community/parse-server/pull/7079

I already use this PR on production for my company for over 1 year to:

  • Support 2FA login (email + password + SMS OTP)
  • Biometric authentification via Webauthn
  • Fully custom auth pattern (a username + a field 1 + a field 2 + SMS OTP)

It works well !

Currently the feature wait a PR from @davimacedo (https://github.com/parse-community/parse-server/pull/7079#issuecomment-990338449) to add some warnings.

Here the example of the new Auth Adapter Interface applied to Webauthn: https://github.com/parse-community/parse-server/pull/7079/files#diff-51ec90d71b4547d4642872376195afc01ee0a8ddc62d3647cfdbc4bfacb79d8d

Here an example of an SMS OTP Auth Adapter (code is not complete):

export const OTPAuth = {
	policy: 'additional',

	async challenge(
		challengeData: boolean,
		authData: OtpAuthDataInput,
		options: any,
		req: any,
		user?: Parse.User,
	) {
		if (!user || !user.get('phone')) throw new Error('User not found')
		const otp = new OTP(user.id, user.get('phone'))
		await SMSAdapter.send(
			user.get('phone'),
			// Dependency injection strategy
			getOTPMessage(user.get('type'))(await otp.generate()),
		)
	},

	async validateSetUp(
		authData: CreateUpdateOtpAuthDataInput,
		options: any,
		req: AuthReq,
	) {
		return checkAuthorization(req)
	},

	async validateUpdate(
		authData: CreateUpdateOtpAuthDataInput,
		options: any,
		req: AuthReq,
	) {
		return checkAuthorization(req)
	},

	async validateLogin(
		authData: OtpAuthDataInput,
		options: any,
		req: AuthReq,
		user?: Parse.User,
	) {
        try {
            // As an additional auth system user should be already identified
            // with another default auth system
            if (!user?.id) return throwError()
            if (!user.get('phone')) return throwError()
            const otp = new OTP(user.id, user.get('phone'))
            await otp.check(authData.code)
            return { doNotSave: true }
        } catch (e: any) {
            // eslint-disable-next-line @typescript-eslint/no-throw-literal
            throw new Parse.Error(403, e.message)
        }
	},
}

@Taylorsuk all the magic happen with the policy: 'additional'.
The architecture is pretty simple.

Here the client example of the 2FA (using GraphQL API).

First you need to challenge with authData or username/passowrd to obtain the SMS OTP. The challenge function will only success if provided authData or username/password are correct.

mutation loginChallenge {
	challenge(input: { challengeData: { otp: true }, username: "[email protected]test.test", password: "aPassword"}) {
		clientMutationId
	}
}

Then the use will receive the OTP on his phone

In your client app you will juste need to call now login like with additional authData

mutation login {
	logIn(input: { usnername: "[email protected]", password: "aPassword", authData: { otp : "123765"} }) {
		viewer {
			sessionToken
			user {
				id
			}
		}
	}
}

Hi @Moumouls, thanks for replying and providing such details. I am sorry it’s taken me a little time to respond.

Great work on your proposed changes to the AuthAdaptor and ability to use WebAuthn.

The example above looks to use SMS for the OTP, however, I wondered if there is an ability to use an authenticator (Google, Authy, 1Password etc). I am not completely sure on the implications of adding this MFA method to the mix, however, it seems to be a popular offering for 2FA around the web (noting that Stripe still uses SMS!).

Is there any plan to update the docs on how to do this? I think 2FA being supported and documented (with examples) capability of Parse, would be a huge plus. A quick scan over the code and commits, I’m still not 100% sure how I would implement this, however more than happy to offer some support in writing the docs if it can first be explained to me.

Once again, thanks for your awesome work and commitment to driving Parse forwards :+1:.

Hi @Taylorsuk i’m happy to confirm that an implementation of a TOTP MFA Adapter will for sure play well with the new auth architecture and it will be super easy to implement.

Since i managed to implement Webauthn, implementing TOTP should be also feasible.

Parse is not completely magic, for a complete TOTP user experience you will need to do some (tiny) work like displaying the private key MFA code (and also rescue keys) as QR code on your client app to allow your users to easily register the private key on Google authenticator or others TOTP managers app.

A QR code could be generated on the client app with some third party packages like: https://www.npmjs.com/package/qrcode

I’ll we need this kind of MFA TOTP adapter for my company so, i’ll work on it but i currently no have ETA (may be april/may 2022). Once it’s implemented some examples will be added.

Important note: the new auth architecture need some non breaking changes on SDKs, in the first instance i think only JS SDK, Rest API, GraphQL API will be compatible with these new features/adapters.

What SDK do you use ? @Taylorsuk

Sounds awesome. Yeah, the client-side workflow is something to work with, however, it’s fairly trivial in the grand scheme of things.

Looking forward to seeing this progress and timelines are not a massive issue.

We’re using Rest.

Hey gents, been looking for this feature since forever, any updates on it being merged and available in parse ?

We’ve not got around to trying it. I needed to see if there is going to be a recommended 2FA example to come out before we tackled it.