To build a custom login screen in OutSystems, create a new screen with Anonymous role access and call the Users module's User_Login Server Action from a Client Action. This tutorial covers the full authentication flow including branded login, self-registration with User_CreateOrUpdate, password reset via User_ResetPassword, and redirecting unauthenticated users to your custom screen instead of the default /Users page.
The OutSystems Authentication Architecture
OutSystems ships with a built-in Users application at `/Users` that handles login, registration, and password management. For production apps you almost always want a branded experience that matches your application's design. In O11, you achieve this by creating your own screens that call the Users module's system actions (User_Login, User_CreateOrUpdate, User_ResetPassword) as Server Actions. In ODC, the auth screens are configured in ODC Portal and the same system actions are available. This tutorial walks through building the three core flows — login, registration, and password reset — with proper form validation, error handling, and redirect logic.
Prerequisites
- An OutSystems 11 module with Reactive Web app type open in Service Studio
- Reference to the Users module added via Manage Dependencies (Ctrl+Q → search 'Users' → add User_Login, User_CreateOrUpdate, User_ResetPassword)
- SMTP configured in Service Center for password reset emails (Service Center → Administration → Email)
- Basic familiarity with Service Studio screen creation and action flows
Step-by-step guide
Add Users Module Dependencies
Add Users Module Dependencies
Open Service Studio with your module. Press **Ctrl+Q** (Manage Dependencies). In the Producer search box, type **Users**. Expand the Users module → Logic section → check these actions: - **User_Login** — authenticates a user - **User_Logout** — ends the session - **User_CreateOrUpdate** — creates or updates a user account - **User_ResetPassword** — sends a password reset email - **User_ChangePassword** — changes password with old password verification Also add **Structures** → **User** (for user data) and **Entities** → **User** (for querying). Click **Apply** → Service Studio adds the dependencies. TrueChange confirms there are no broken references.
Expected result: Logic tab → Server Actions shows User_Login, User_CreateOrUpdate, User_ResetPassword as available referenced actions from the Users module.
Create the Custom Login Screen
Create the Custom Login Screen
Go to **Interface tab → UI Flows → MainFlow** (or your main UI flow). Right-click → **Add Screen** → choose **Empty** template → name it `Login`. Set **Roles** property to `Anonymous` so unauthenticated users can access it. From the Toolbox, drag a **Form** widget onto the canvas. Inside the Form, add: - **Input** widget → name it `UsernameInput` → Variable: create Local Variable `Username` (Text) - **Input** widget → name it `PasswordInput` → Variable: create Local Variable `Password` (Text) → InputType: Password - **Button** widget → Label: 'Sign In' → Is Form Default: Yes Add a **Text** widget below the form for error messages → name it `ErrorMessage` → bind Value to a Local Variable `LoginError` (Text) → Visible: `LoginError <> ""`
Expected result: The Login screen is visible in the Interface tab with a Form containing two inputs and a Sign In button. The screen is accessible to Anonymous users.
Implement the Login Server Action
Implement the Login Server Action
Double-click the **Sign In** button → Service Studio asks to create or select a client action → create `OnSignInClick` Client Action. Since User_Login requires a server call, create a new **Server Action**: Logic tab → Server Actions → right-click → **Add Server Action** → name it `DoLogin`. Add Input Parameters: `Username` (Text), `Password` (Text). Add Output Parameters: `Success` (Boolean), `ErrorMessage` (Text). Action flow: ``` Start → User_Login Username = Username Password = Password PersistLogin = True → If User_Login.Success [True] → Assign Success = True → End [False] → Assign Success = False, ErrorMessage = "Invalid username or password" → End ``` In `OnSignInClick` Client Action: ``` Start → DoLogin (call server action) Username = UsernameInput.Value Password = PasswordInput.Value → If DoLogin.Success [True] → Navigate to Home screen [False] → Assign LoginError = DoLogin.ErrorMessage → End ```
1/* DoLogin Server Action — Output parameters */2Success: Boolean3ErrorMessage: Text45/* Action Flow */6Start7→ User_Login8 Username = Username /* Input parameter */9 Password = Password /* Input parameter */10 PersistLogin = True /* Keeps session across browser restarts */11→ If User_Login.Success12 [True]13 → Assign: Success = True14 → End15 [False]16 → Assign: Success = False17 → Assign: ErrorMessage = "Invalid username or password"18 → EndExpected result: Clicking Sign In with valid credentials navigates to the Home screen. Invalid credentials show the error message text without revealing which field was wrong.
Build the Registration Screen
Build the Registration Screen
In **Interface tab → UI Flows → MainFlow**, right-click → **Add Screen** → Empty → name it `Register`. Set Roles to `Anonymous`. Create a Form with these inputs (each bound to a Local Variable): - `FullName` (Text) - `Email` (Text) → InputType: email, Mandatory: Yes - `Password` (Text) → InputType: Password, Mandatory: Yes - `ConfirmPassword` (Text) → InputType: Password, Mandatory: Yes Create Server Action `RegisterUser` with these inputs. Action flow: ``` Start → If Password = ConfirmPassword [False] → Assign ErrorMessage = "Passwords do not match" → End → GetUserByUsername (Aggregate: filter User.Username = Email) → If NOT GetUserByUsername.List.Empty [True] → Assign ErrorMessage = "An account with this email already exists" → End → User_CreateOrUpdate User.Username = Email User.Password = Password User.Name = FullName User.Email = Email User.IsActive = True → User_Login (auto-login after registration) Username = Email Password = Password → End (Success = True) ```
1/* RegisterUser Server Action inputs */2FullName: Text3Email: Text4Password: Text5ConfirmPassword: Text67/* Outputs */8Success: Boolean9ErrorMessage: Text1011/* Aggregate: GetUserByUsername */12/* Sources: User entity */13/* Filter: User.Username = Email */1415/* User_CreateOrUpdate key parameters */16User.Username = Email17User.Email = Email18User.Name = FullName19User.Password = Password /* Stored as salted hash by OutSystems */20User.IsActive = TrueExpected result: Submitting the registration form with valid data creates the user and logs them in automatically, navigating to the Home screen. Duplicate email or password mismatch shows the appropriate inline error.
Implement Password Reset (Forgot Password Flow)
Implement Password Reset (Forgot Password Flow)
Create two screens: 1. `ForgotPassword` — collects the user's email address (Roles: Anonymous) 2. `ResetPassword` — takes a reset token from the URL and sets a new password (Roles: Anonymous) **ForgotPassword screen logic** (Server Action `SendResetEmail`): ``` Start → GetUserByEmail (Aggregate: filter User.Email = InputEmail) → If NOT GetUserByEmail.List.Empty [True] → User_ResetPassword UserId = GetUserByEmail.List.Current.User.Id [This sends an email with reset link automatically] → /* Always show success message regardless — prevent email enumeration */ → Assign SuccessMessage = "If an account exists, a reset link has been sent." → End ``` **ResetPassword screen** must receive `?NewPasswordKey=<token>` as an Input Parameter from the URL. The Users module's reset email links to `/[YourModule]/ResetPassword?NewPasswordKey=TOKEN`. Set screen Input Parameter `NewPasswordKey` (Text). ResetPassword logic (Server Action `DoResetPassword`): ``` Start → User_ChangePasswordWithResetKey UserId = ... (from token lookup) NewPasswordKey = NewPasswordKey NewPassword = NewPassword → On Success: Navigate to Login ```
1/* ForgotPassword screen — Input Variables */2InputEmail: Text (bound to Email input)34/* SendResetEmail Server Action */5Start6→ GetUserByEmail7 /* Aggregate filter: User.Email = InputEmail AND User.IsActive = True */8→ If NOT GetUserByEmail.List.Empty9 [True]10 → User_ResetPassword11 UserId = GetUserByEmail.List.Current.User.Id12→ Assign13 ShowConfirmation = True14→ End1516/* ResetPassword screen — Input Parameters */17NewPasswordKey: Text /* Passed in URL: ?NewPasswordKey=abc123 */1819/* The email generated by User_ResetPassword automatically20 contains a link to:21 https://[environment]/[module]/ResetPassword?NewPasswordKey=[token]22 Configure the reset email template in Service Center →23 Administration → Email → Users_ResetPassword template */Expected result: Submitting the ForgotPassword form sends a reset email (visible in Service Center → Monitoring → Emails). Clicking the link in the email opens the ResetPassword screen with the token pre-filled in the URL.
Redirect to Custom Login Screen Instead of /Users
Redirect to Custom Login Screen Instead of /Users
By default, OutSystems redirects unauthenticated users to the built-in `/Users` login screen. To redirect to your custom Login screen instead: In Service Studio, go to **Data tab → Site Properties**. Look for Site Property `DefaultLoginScreen` (added by referencing the Users module). Set its **Default Value** to the path of your custom Login screen. Alternatively, in your module's **Exceptional Handling** flow: Interface tab → UI Flows → Common → **OnException** flow → update the Security Exception handler to navigate to your Login screen instead of the default. For Reactive Web apps, the most reliable method is: Interface tab → UI Flows → Common → **InvalidPermissions** screen → replace its content with a redirect Client Action that navigates to your Login screen with the current URL as a ReturnURL parameter.
1/* InvalidPermissions screen — OnInitialize Client Action */2/* Replace default content with redirect to custom login */34Start5→ Navigate to Screen: Login6 ReturnURL = GetOwnerURLPath()7→ End89/* Login screen OnInitialize — handle ReturnURL after login */10/* In OnSignInClick after DoLogin.Success = True: */11If ReturnURL <> "" Then12 Navigate External: ReturnURL13Else14 Navigate to Screen: Home15End IfExpected result: Navigating to any protected screen without being logged in redirects to your branded custom Login screen rather than the generic /Users page. After login, the user is returned to the screen they originally requested.
Complete working example
1/* ============================================================2 OutSystems Custom Auth — Complete Action Flow Reference3 ============================================================ */456/* === 1. DoLogin Server Action === */7/* Inputs: Username (Text), Password (Text) */8/* Outputs: Success (Boolean), ErrorMessage (Text) */910Start11→ User_Login12 Username = Username13 Password = Password14 PersistLogin = True15→ If User_Login.Success16 [True] → Assign Success = True → End17 [False] → Assign Success = False18 Assign ErrorMessage = "Invalid username or password"19 → End202122/* === 2. RegisterUser Server Action === */23/* Inputs: FullName (Text), Email (Text), */24/* Password (Text), ConfirmPassword (Text) */25/* Outputs: Success (Boolean), ErrorMessage (Text) */2627Start28→ If Password <> ConfirmPassword29 [True] → Assign ErrorMessage = "Passwords do not match"30 → Assign Success = False → End31→ GetUserByEmail /* Aggregate: User.Email = Email */32→ If NOT GetUserByEmail.List.Empty33 [True] → Assign ErrorMessage = "Email already registered"34 → Assign Success = False → End35→ User_CreateOrUpdate36 User.Username = Email37 User.Email = Email38 User.Name = FullName39 User.Password = Password40 User.IsActive = True41→ If User_CreateOrUpdate.Success42 [True] → User_Login43 Username = Email44 Password = Password45 PersistLogin = True46 → Assign Success = True → End47 [False] → Assign ErrorMessage = "Registration failed. Please try again."48 → Assign Success = False → End495051/* === 3. SendResetEmail Server Action === */52/* Input: Email (Text) */53/* Output: Sent (Boolean) */5455Start56→ GetUserByEmail /* Aggregate: User.Email = Email AND User.IsActive = True */57→ If NOT GetUserByEmail.List.Empty58 [True] → User_ResetPassword59 UserId = GetUserByEmail.List.Current.User.Id60/* Always return True regardless — prevent email enumeration */61→ Assign Sent = True62→ End636465/* === 4. Widget expressions === */6667/* Error message Visible property */68LoginError <> ""6970/* Confirm password validation in Client Action before server call */71PasswordInput.Value = ConfirmPasswordInput.ValueCommon mistakes
Why it's a problem: Placing User_Login inside a Client Action instead of a Server Action — User_Login must run server-side and is not available in Client Actions.
How to avoid: Create a Server Action (e.g., DoLogin) that calls User_Login, then call this Server Action from your Client Action's OnClick handler.
Why it's a problem: Forgetting to check Form.Valid before calling the login Server Action, allowing empty-field submissions that produce confusing error messages.
How to avoid: In the OnSignInClick Client Action, add an If node: If Form.Valid → [True] call DoLogin / [False] show validation errors. The Form widget automatically sets Form.Valid to False when any Mandatory input is empty.
Why it's a problem: Creating the registration form with a Text data type for the Email field instead of Email data type, missing built-in format validation.
How to avoid: Set the Username and Email input widgets' Variable data type to Email (not Text) — OutSystems validates the email format automatically and shows a validation message.
Why it's a problem: Not setting the Roles property on the Login and Register screens to Anonymous, causing a redirect loop where unauthenticated users are sent to the login screen that requires authentication to view.
How to avoid: Select the Login and Register screens in Interface tab → set Roles property to include Anonymous so unauthenticated users can access these screens without being redirected.
Best practices
- Always use a generic error message like 'Invalid username or password' — never distinguish between wrong username and wrong password, as this leaks account existence information.
- Implement rate limiting on the login Server Action using an entity-based attempt counter per IP or username — OutSystems does not include built-in brute-force protection.
- Use HTTPS only — configure your OutSystems environment to redirect HTTP to HTTPS in IIS or the load balancer before the login screen is ever served.
- Password reset emails should always show a success confirmation even if the email is not found — this prevents email enumeration attacks.
- Set PersistLogin = False for internal tools where session expiry on browser close is appropriate; set True for consumer-facing apps where 'stay logged in' is expected.
- Validate email format using the built-in Email data type for the Username field rather than a Text field — OutSystems validates email format automatically when the attribute type is Email.
- Log failed login attempts to an entity or Service Center error log to detect brute-force patterns without exposing the log to end users.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a custom login screen in OutSystems 11 Reactive Web app. I need: (1) a branded login screen with Username and Password inputs that calls User_Login server-side and redirects on success, (2) a registration form that calls User_CreateOrUpdate with email as username, (3) a forgot-password flow that calls User_ResetPassword and sends a reset email, and (4) the InvalidPermissions screen redirecting to my custom login instead of /Users. Show me the action flows using arrow notation and the OutSystems expression syntax for form validation.
In my OutSystems Service Studio module (Reactive Web), help me: add the Users module dependency (User_Login, User_CreateOrUpdate, User_ResetPassword) via Manage Dependencies, create a DoLogin Server Action that returns Success/ErrorMessage, wire it to an OnSignInClick Client Action on the Login screen, handle the ReturnURL redirect after successful login, and set the Login screen Roles property to Anonymous. Use exact Service Studio UI paths.
Frequently asked questions
Can I use a custom login screen with both password auth and SSO in the same app?
Yes. Add a 'Sign in with SSO' button that calls GetLoginUrl() from the IdP Forge component, and a standard username/password form that calls User_Login. Both flows can coexist on the same screen. Users choose which method to use.
Where does OutSystems store user passwords — can I query them?
Passwords are stored as salted SHA-512 hashes in the Users entity. You cannot and should not query or retrieve plaintext passwords. Use User_Login for authentication and User_ResetPassword for password recovery — never build your own password validation logic.
How do I customize the password reset email template?
Go to Service Center → Administration → Email. Find the email template named after your Users module email action (typically UserRegistrationEmail or ChangePasswordEmail). Edit the HTML template directly in Service Center. Note that template changes here do not require a module republish.
What is the difference between User_CreateOrUpdate and User_Create in OutSystems?
User_CreateOrUpdate either creates a new user or updates an existing one based on the Username field — it never creates duplicates. User_Create always creates a new record and throws a Database Exception if a user with the same Username already exists. For registration flows, User_CreateOrUpdate is safer; for strict insert-only scenarios, use User_Create with an explicit duplicate check first.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation