Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

Building Custom Login, Registration, and Password Reset Flows in OutSystems

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.

What you'll learn

  • Create a branded custom login screen using the Users module's User_Login Server Action
  • Build a self-registration form that creates OutSystems user accounts with User_CreateOrUpdate
  • Implement a password reset flow using User_ResetPassword and a tokenized email link
  • Redirect unauthorized users to your custom login screen instead of the default /Users page
  • Understand the key differences between O11 Users module customization and ODC built-in auth screens
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate11 min read45-60 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

2

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.

3

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 ```

typescript
1/* DoLogin Server Action — Output parameters */
2Success: Boolean
3ErrorMessage: Text
4
5/* Action Flow */
6Start
7 User_Login
8 Username = Username /* Input parameter */
9 Password = Password /* Input parameter */
10 PersistLogin = True /* Keeps session across browser restarts */
11 If User_Login.Success
12 [True]
13 Assign: Success = True
14 End
15 [False]
16 Assign: Success = False
17 Assign: ErrorMessage = "Invalid username or password"
18 End

Expected 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.

4

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) ```

typescript
1/* RegisterUser Server Action inputs */
2FullName: Text
3Email: Text
4Password: Text
5ConfirmPassword: Text
6
7/* Outputs */
8Success: Boolean
9ErrorMessage: Text
10
11/* Aggregate: GetUserByUsername */
12/* Sources: User entity */
13/* Filter: User.Username = Email */
14
15/* User_CreateOrUpdate key parameters */
16User.Username = Email
17User.Email = Email
18User.Name = FullName
19User.Password = Password /* Stored as salted hash by OutSystems */
20User.IsActive = True

Expected 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.

5

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 ```

typescript
1/* ForgotPassword screen — Input Variables */
2InputEmail: Text (bound to Email input)
3
4/* SendResetEmail Server Action */
5Start
6 GetUserByEmail
7 /* Aggregate filter: User.Email = InputEmail AND User.IsActive = True */
8 If NOT GetUserByEmail.List.Empty
9 [True]
10 User_ResetPassword
11 UserId = GetUserByEmail.List.Current.User.Id
12 Assign
13 ShowConfirmation = True
14 End
15
16/* ResetPassword screen — Input Parameters */
17NewPasswordKey: Text /* Passed in URL: ?NewPasswordKey=abc123 */
18
19/* The email generated by User_ResetPassword automatically
20 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.

6

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.

typescript
1/* InvalidPermissions screen — OnInitialize Client Action */
2/* Replace default content with redirect to custom login */
3
4Start
5 Navigate to Screen: Login
6 ReturnURL = GetOwnerURLPath()
7 End
8
9/* Login screen OnInitialize — handle ReturnURL after login */
10/* In OnSignInClick after DoLogin.Success = True: */
11If ReturnURL <> "" Then
12 Navigate External: ReturnURL
13Else
14 Navigate to Screen: Home
15End If

Expected 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

CustomAuthActions.os-flow
1/* ============================================================
2 OutSystems Custom Auth Complete Action Flow Reference
3 ============================================================ */
4
5
6/* === 1. DoLogin Server Action === */
7/* Inputs: Username (Text), Password (Text) */
8/* Outputs: Success (Boolean), ErrorMessage (Text) */
9
10Start
11 User_Login
12 Username = Username
13 Password = Password
14 PersistLogin = True
15 If User_Login.Success
16 [True] Assign Success = True End
17 [False] Assign Success = False
18 Assign ErrorMessage = "Invalid username or password"
19 End
20
21
22/* === 2. RegisterUser Server Action === */
23/* Inputs: FullName (Text), Email (Text), */
24/* Password (Text), ConfirmPassword (Text) */
25/* Outputs: Success (Boolean), ErrorMessage (Text) */
26
27Start
28 If Password <> ConfirmPassword
29 [True] Assign ErrorMessage = "Passwords do not match"
30 Assign Success = False End
31 GetUserByEmail /* Aggregate: User.Email = Email */
32 If NOT GetUserByEmail.List.Empty
33 [True] Assign ErrorMessage = "Email already registered"
34 Assign Success = False End
35 User_CreateOrUpdate
36 User.Username = Email
37 User.Email = Email
38 User.Name = FullName
39 User.Password = Password
40 User.IsActive = True
41 If User_CreateOrUpdate.Success
42 [True] User_Login
43 Username = Email
44 Password = Password
45 PersistLogin = True
46 Assign Success = True End
47 [False] Assign ErrorMessage = "Registration failed. Please try again."
48 Assign Success = False End
49
50
51/* === 3. SendResetEmail Server Action === */
52/* Input: Email (Text) */
53/* Output: Sent (Boolean) */
54
55Start
56 GetUserByEmail /* Aggregate: User.Email = Email AND User.IsActive = True */
57 If NOT GetUserByEmail.List.Empty
58 [True] User_ResetPassword
59 UserId = GetUserByEmail.List.Current.User.Id
60/* Always return True regardless — prevent email enumeration */
61 Assign Sent = True
62 End
63
64
65/* === 4. Widget expressions === */
66
67/* Error message Visible property */
68LoginError <> ""
69
70/* Confirm password validation in Client Action before server call */
71PasswordInput.Value = ConfirmPasswordInput.Value

Common 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.

ChatGPT Prompt

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.

OutSystems Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.