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
2. OTP Verification
On the OTP Verification screen:
When the user submits the OTP:
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:
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:
Any feedback, suggestions, or alternative approaches would be greatly appreciated.
Thanks!
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.