Module: Parse::MFA

Defined in:
lib/parse/two_factor_auth.rb,
lib/parse/two_factor_auth/user_extension.rb

Overview

Multi-Factor Authentication (MFA) support for Parse Server.

This module interfaces with Parse Server’s built-in MFA adapter which supports TOTP (Time-based One-Time Password) and SMS-based authentication.

Parse Server Configuration

MFA must be enabled in your Parse Server configuration:

{
  auth: {
    mfa: {
      enabled: true,
      options: ["TOTP"],  // or ["SMS", "TOTP"]
      digits: 6,
      period: 30,
      algorithm: "SHA1"
    }
  }
}

TOTP Setup Flow

  1. Generate a secret client-side using MFA.generate_secret

  2. Display QR code to user using MFA.provisioning_uri or MFA.qr_code

  3. User scans QR with authenticator app (Google Authenticator, Authy, etc.)

  4. User enters the 6-digit code from their app

  5. Call UserExtension#setup_mfa! with secret and token to enable MFA

  6. Store the recovery codes returned - user needs these for account recovery!

Examples:

Enable TOTP MFA for a user

# Step 1: Generate secret
secret = Parse::MFA.generate_secret

# Step 2: Show QR code to user
qr_svg = Parse::MFA.qr_code(secret, user.email, issuer: "MyApp")
# render qr_svg in your UI

# Step 3-4: User scans and enters code
token = params[:totp_code]  # "123456" from authenticator app

# Step 5: Enable MFA
recovery_codes = user.setup_mfa!(secret: secret, token: token)
# => "ABC123DEF456..., XYZ789..."

# Step 6: Show recovery codes to user (one time only!)

Login with MFA

user = Parse::User.("username", "password", "123456")

See Also:

Defined Under Namespace

Modules: UserExtension Classes: AlreadyEnabledError, DependencyError, ForbiddenError, NotEnabledError, RequiredError, VerificationError

Constant Summary collapse

DEFAULT_CONFIG =

Default configuration

{
  issuer: "Parse App",
  digits: 6,
  period: 30,
  algorithm: "SHA1",
  secret_length: 20,  # Minimum required by Parse Server
}.freeze

Class Method Summary collapse

Class Method Details

.build_login_auth_data(token:) ⇒ Hash

Build authData hash for MFA login.

Parameters:

  • token (String)

    TOTP code or recovery code

Returns:

  • (Hash)

    authData for Parse Server



265
266
267
268
269
270
271
# File 'lib/parse/two_factor_auth.rb', line 265

def (token:)
  {
    mfa: {
      token: token,
    },
  }
end

.build_setup_auth_data(secret:, token:) ⇒ Hash

Build authData hash for MFA setup.

Parameters:

  • secret (String)

    Base32-encoded TOTP secret

  • token (String)

    Current TOTP code for verification

Returns:

  • (Hash)

    authData for Parse Server



252
253
254
255
256
257
258
259
# File 'lib/parse/two_factor_auth.rb', line 252

def build_setup_auth_data(secret:, token:)
  {
    mfa: {
      secret: secret,
      token: token,
    },
  }
end

.build_sms_confirm_auth_data(mobile:, token:) ⇒ Hash

Build authData hash for SMS MFA confirmation.

Parameters:

  • mobile (String)

    Phone number

  • token (String)

    SMS code received

Returns:

  • (Hash)

    authData for Parse Server



290
291
292
293
294
295
296
297
# File 'lib/parse/two_factor_auth.rb', line 290

def build_sms_confirm_auth_data(mobile:, token:)
  {
    mfa: {
      mobile: mobile,
      token: token,
    },
  }
end

.build_sms_setup_auth_data(mobile:) ⇒ Hash

Build authData hash for SMS MFA setup.

Parameters:

  • mobile (String)

    Phone number in E.164 format

Returns:

  • (Hash)

    authData for Parse Server



277
278
279
280
281
282
283
# File 'lib/parse/two_factor_auth.rb', line 277

def build_sms_setup_auth_data(mobile:)
  {
    mfa: {
      mobile: mobile,
    },
  }
end

.configHash

Global MFA configuration

Returns:



107
108
109
# File 'lib/parse/two_factor_auth.rb', line 107

def config
  @config ||= DEFAULT_CONFIG.dup
end

.configure {|config| ... } ⇒ Object

Configure MFA settings

Examples:

Parse::MFA.configure do |config|
  config[:issuer] = "My App"
end

Yields:

  • (config)

    Configuration hash



117
118
119
120
# File 'lib/parse/two_factor_auth.rb', line 117

def configure
  yield config if block_given?
  config
end

.current_code(secret) ⇒ String

Get the current TOTP code (for testing/debugging).

Parameters:

  • secret (String)

    Base32-encoded secret

Returns:

  • (String)

    Current 6-digit code



194
195
196
197
# File 'lib/parse/two_factor_auth.rb', line 194

def current_code(secret)
  ensure_rotp!
  totp(secret).now
end

.generate_secret(length: nil) ⇒ String

Generate a new TOTP secret for MFA setup. The secret must be at least 20 characters (Parse Server requirement).

Examples:

secret = Parse::MFA.generate_secret
# => "JBSWY3DPEHPK3PXP4QFAZJ7K"

Parameters:

  • length (Integer) (defaults to: nil)

    Secret length (minimum 20)

Returns:

  • (String)

    Base32-encoded secret



149
150
151
152
153
154
# File 'lib/parse/two_factor_auth.rb', line 149

def generate_secret(length: nil)
  ensure_rotp!
  length ||= config[:secret_length]
  length = [length, 20].max  # Parse Server requires minimum 20
  ROTP::Base32.random(length)
end

.provisioning_uri(secret, account_name, issuer: nil) ⇒ String

Generate provisioning URI for authenticator apps.

Examples:

uri = Parse::MFA.provisioning_uri(secret, "user@example.com", issuer: "MyApp")
# => "otpauth://totp/MyApp:user@example.com?secret=ABC123&issuer=MyApp"

Parameters:

  • secret (String)

    Base32-encoded secret

  • account_name (String)

    User identifier (email or username)

  • issuer (String) (defaults to: nil)

    Optional issuer override

Returns:



209
210
211
212
# File 'lib/parse/two_factor_auth.rb', line 209

def provisioning_uri(secret, , issuer: nil)
  ensure_rotp!
  totp(secret, issuer: issuer).provisioning_uri()
end

.qr_code(secret, account_name, issuer: nil, format: :svg) ⇒ String

Generate a QR code for the authenticator app.

Examples:

svg = Parse::MFA.qr_code(secret, user.email, issuer: "MyApp")
# Render in HTML: <%= raw svg %>

Parameters:

  • secret (String)

    Base32-encoded secret

  • account_name (String)

    User identifier

  • issuer (String) (defaults to: nil)

    Optional issuer name

  • format (Symbol) (defaults to: :svg)

    Output format (:svg, :png, :ascii)

Returns:

  • (String)

    QR code in specified format



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/parse/two_factor_auth.rb', line 225

def qr_code(secret, , issuer: nil, format: :svg)
  ensure_rqrcode!
  uri = provisioning_uri(secret, , issuer: issuer)
  qr = RQRCode::QRCode.new(uri)

  case format
  when :svg
    qr.as_svg(
      color: "000",
      shape_rendering: "crispEdges",
      module_size: 4,
      standalone: true,
    )
  when :png
    qr.as_png(size: 300)
  when :ascii
    qr.as_ansi
  else
    qr.as_svg
  end
end

.rotp_available?Boolean

Check if rotp gem is available

Returns:

  • (Boolean)


124
125
126
127
128
129
# File 'lib/parse/two_factor_auth.rb', line 124

def rotp_available?
  require "rotp"
  true
rescue LoadError
  false
end

.rqrcode_available?Boolean

Check if rqrcode gem is available

Returns:

  • (Boolean)


133
134
135
136
137
138
# File 'lib/parse/two_factor_auth.rb', line 133

def rqrcode_available?
  require "rqrcode"
  true
rescue LoadError
  false
end

.totp(secret, issuer: nil) ⇒ ROTP::TOTP

Create a TOTP instance for verification.

Parameters:

  • secret (String)

    Base32-encoded secret

  • issuer (String) (defaults to: nil)

    Optional issuer name

Returns:

  • (ROTP::TOTP)


161
162
163
164
165
166
167
168
169
# File 'lib/parse/two_factor_auth.rb', line 161

def totp(secret, issuer: nil)
  ensure_rotp!
  ROTP::TOTP.new(
    secret,
    issuer: issuer || config[:issuer],
    interval: config[:period],
    digits: config[:digits],
  )
end

.verify(secret, code) ⇒ Boolean

Verify a TOTP code locally (for testing/validation before sending to server).

Examples:

if Parse::MFA.verify(secret, "123456")
  puts "Code is valid!"
end

Parameters:

  • secret (String)

    Base32-encoded secret

  • code (String)

    The 6-digit code to verify

Returns:

  • (Boolean)

    True if valid



181
182
183
184
185
186
187
188
# File 'lib/parse/two_factor_auth.rb', line 181

def verify(secret, code)
  return false if secret.blank? || code.blank?

  ensure_rotp!
  drift_seconds = config[:period]
  totp_instance = totp(secret)
  totp_instance.verify(code.to_s, drift_behind: drift_seconds, drift_ahead: drift_seconds).present?
end