How is your experience with the OutSystems Community?
6
Views
1
Comments
Proposed 2FA Approach on ODC – Email OTP / Authenticator App
Discussion

Hi everyone,

I’d like to share a proposed approach for implementing 2FA for External Users on ODC and would appreciate your feedback, especially regarding the security and session-handling aspects of the design.

Background / Problem

ODC does not currently provide a built-in way to enable 2FA for External Users.

In OutSystems 11 (O11), one possible approach is:

Login → Logout → Verify OTP → Login by UserId

However, this approach is no longer applicable in ODC because the Login API does not support logging in by UserId.

I also considered temporarily storing the user's password and reusing it after OTP verification, but this would introduce a significant security risk and should be avoided.

Based on this limitation, I’m considering an OTP-based 2FA approach where the OTP can be obtained through either email or an authenticator application, such as Google Authenticator.

Proposed Flow

1. Initial Login & OTP State

  • The user enters Username/Password and calls the standard ODC Login function.
  • After successful login, the system generates:
    • A random SessionToken (GUID)
    • A 6-digit OTPCode
  • An OTPRequest record is created with:
    • UserId
    • SessionToken
    • OTPCode
    • Expiry (5 minutes)
    • AttemptCount (initially 0)
    • IsVerified (initially False)
  • The OTP can be obtained through either of the following methods:
    • Email OTP: The system sends the OTP code to the user's registered email address.
    • Authenticator App: The user opens an authenticator application, such as Google Authenticator, to obtain the time-based OTP code.
  • The user is then redirected to the OTP Verification screen and enters the code to complete the 2FA verification.

2. OTP Verification

On the OTP Verification screen:

  • On initialization, the application retrieves the SessionToken.
  • If there is no valid token, the user is redirected back to Login.
  • If IsVerified = True, the user can proceed to the application.
  • If the OTP has expired, the OTP request is invalidated and the user is redirected back to Login.

When the user submits the OTP:

  • If AttemptCount >= 5, the OTP request is deleted, the user is logged out, and they are redirected to Login.
  • If the OTP is incorrect, AttemptCount is increased and an error message is displayed.
  • If the OTP is correct, IsVerified is updated to True, and the user can proceed to the application.

3. Application Protection

For UI protection, instead of adding verification logic to every screen's OnInitialize, I’m considering placing the validation logic in the Common Layout / Block Layout used by the application.

The layout would validate the SessionToken and verification state before allowing the user to access the application:

  • Missing or invalid token → Redirect to Login
  • Valid token but IsVerified = False → Redirect to OTP Verification
  • IsVerified = True → Allow access

The intention is to avoid duplicating authentication checks across individual screens and prevent application data logic from being executed before the user completes 2FA verification.

For REST APIs, similar protection would be added at the OnRequest level to prevent unauthorized access from outside the UI, returning an appropriate 401/403 response.

Authenticator App Option

Instead of relying only on email OTP, another option is to use a TOTP-based authenticator application, such as Google Authenticator.

In this approach, the user would register their authenticator application and use the time-based code generated by the application during login.

I’m not sure yet whether this would be the more appropriate approach for ODC, or whether there are any ODC-specific recommendations or limitations that we should consider.

Request for Feedback

Could you please review this proposed approach and share your thoughts?

In particular, I’d like to get feedback on:

  • Is the proposed OTP + SessionToken + verification state approach reasonable from a security and architectural perspective?
  • Would using an Authenticator App / TOTP be a better approach than Email OTP?
  • Are there any ODC-specific limitations or recommended patterns for implementing 2FA?
  • Are there any security concerns or edge cases that I may have missed?
  • Is there a more recommended approach for handling the session after the initial ODC login and before 2FA verification is completed?

Any feedback, suggestions, or alternative approaches would be greatly appreciated.

Thanks!

2025-12-15 12-14-19
Ajay Sharma

Hi Anh Tuan, thoughtful write-up — you've clearly thought this through. Feedback on your questions:

The one thing I'd flag first: in your flow the user completes the standard ODC Login() before OTP verification, so from that point they hold a fully valid ODC session. That makes your 2FA a second gate on an already-authenticated session, not part of authentication. The risk: your Common Layout check protects the UI, but anyone calling a screen's data actions, a server action, or an exposed REST endpoint directly (with that valid session) bypasses the layout. So IsVerified has to be enforced server-side on every sensitive data action and API, not just the layout. Your OnRequest 401/403 idea is exactly right — extend that same mandatory check to server actions too.

Hardening the OTP + SessionToken design: store the OTP hashed, not plaintext, and compare hashes. Bind the SessionToken to the logged-in UserId server-side (never trust it from the client) and make it single-use. You cap attempts (good) — also throttle OTP generation/resend to stop email-bombing across regenerations. Enforce expiry server-side, and make sure a fresh Login() always issues a new token and resets IsVerified.

Email OTP vs Authenticator (TOTP): TOTP is stronger — email OTP inherits every weakness of the email channel (delay, interception, compromised inbox = compromised 2FA). On ODC there's no built-in TOTP, but it's a clean fit for External Logic (C#) — a library like Otp.NET handles RFC 6238 generate/validate, and you show the enrollment QR as an otpauth:// URI. Store each user's TOTP secret encrypted at rest. Common pattern: email OTP as fallback, TOTP as primary.

The ODC-native option worth considering: if these external users can sit behind an external IdP, ODC supports external identity providers (OIDC, SAML 2.0, Entra ID) for end-user auth. Delegating to an IdP that already enforces MFA gives you 2FA without hand-rolling OTP, session-gating, or secret storage — the IdP owns that whole problem. If a custom in-app flow is a hard requirement, your approach is a pragmatic fallback; just lean on the server-side enforcement above.

Docs on the IdP route: https://success.outsystems.com/documentation/outsystems_developer_cloud/user_management/configuring_authentication_with_external_identity_providers/

Happy to go deeper on any of these.

Community GuidelinesBe kind and respectful, give credit to the original source of content, and search for duplicates before posting.