Skip to content

Commit d20e2b3

Browse files
authored
Merge pull request #1753 from Platform-OS/user-module
Add User module tutorial
2 parents 60948e3 + 6104c95 commit d20e2b3

16 files changed

+1030
-0
lines changed
116 KB
Loading
63 KB
Loading
111 KB
Loading
104 KB
Loading
119 KB
Loading
147 KB
Loading
46.9 KB
Loading
93.8 KB
Loading
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
metadata:
3+
title: Creating a User Account
4+
description: Learn how to create a user account using the built-in registration flow of the platformOS User Module.
5+
converter: markdown
6+
---
7+
8+
Now that the User Module is installed, the homepage is set up, and you are aware of the available endpoints, you can create your first account on your instance. The module provides the complete registration flow out of the box, so you do not need to build any forms or mutations at this stage. In this chapter, you will register a new user, explore how platformOS stores user credentials, and verify the newly created account using the `pos-cli GUI`.
9+
10+
## Step 1: Open the Registration Page
11+
12+
The User Module automatically exposes a **sign-up form** at the following URL:
13+
14+
```
15+
/users/new
16+
```
17+
<br>
18+
19+
Navigate to: `https://YOUR-INSTANCE.staging.oregon.platform-os.com/users/new`
20+
21+
You should see a registration page. If Common Styling is configured correctly, the form will already appear clean and fully styled.
22+
23+
## Step 2: Create an Account
24+
25+
Fill in the form with:
26+
27+
* a **valid email format** (does not have to be a real inbox), and
28+
* a **password** you will remember for upcoming steps.
29+
30+
<img loading="lazy" src="{{ 'images/get-started/user-management/create-account.png' | asset_url }}" alt="Creating an account using the User Module">
31+
32+
Submit the form.
33+
34+
If the registration is successful:
35+
36+
* a new account is created,
37+
* you have been redirected to the home page and you are automatically logged in,
38+
* but you **do not see any confirmation yet** because the layout has no navigation or user state indicators.
39+
40+
We will add login/logout awareness later in the tutorial.
41+
42+
{% include 'alert/tip', content: 'If you receive validation errors, correct the issues and try again. The User Module includes built-in validations for email format and password requirements.' %}
43+
44+
## Step 3: Verify the User using the GUI
45+
46+
To confirm that your account was created, use the admin GUI provided by `pos-cli`.
47+
48+
Start the GUI:
49+
50+
<pre class="command-line" data-user="user" data-host="host"><code class="language-bash">
51+
pos-cli gui serve staging
52+
</code></pre>
53+
54+
This command starts a local admin interface at:
55+
56+
```
57+
http://localhost:3333
58+
```
59+
60+
Open it and select **Users** from the navigation. You should now see the user you just registered, along with the email address you provided.
61+
62+
<img loading="lazy" src="{{ 'images/get-started/user-management/user-in-users.png' | asset_url }}" alt="Viewing the newly created user in the pos-cli GUI">
63+
64+
## Step 4: Understand How User Credentials Are Stored
65+
66+
platformOS does not store passwords in plain text. Instead, it uses **[Bcrypt](https://bcrypt-generator.com/)**, a one-way cryptographic hashing function. This ensures several security guarantees:
67+
68+
* The original password **cannot** be recovered from the stored hash.
69+
* When a user logs in, the password they enter is **hashed again** using the same algorithm, and the hashes are compared.
70+
* If the hashes match, the user is authenticated — without the system ever knowing the real password.
71+
72+
You can think of it like burning a piece of paper containing the password into ash. Once destroyed, the original password cannot be reconstructed.
73+
74+
This hashing mechanism is built into the User Module. You do not need to configure anything manually.
75+
76+
## Step 5: Users vs. Records in platformOS
77+
78+
So far, you have worked mostly with **Records** (for example, in the Contact Us tutorial). A `record` is a flexible data object defined through your custom schema. A **User** is not a regular Record.
79+
80+
### Why Users are separate from Records
81+
82+
Users include several built-in fields and guarantees that Records do not provide:
83+
84+
* **Email uniqueness**: The system ensures that no two users can register with the same email address.
85+
86+
* **Secure password handling**: Passwords are never stored in plain text. Whatever value you submit in the `password` field is automatically hashed before being stored.
87+
88+
This distinction is important. Users are not stored as regular Records because they include specialized fields, security considerations, and authentication behaviors.
89+
90+
### Authentication features built into the User entity
91+
92+
In addition to secure password storage, the User entity provides authentication features out of the box:
93+
94+
* **Session-based authentication** using standard login/logout flows.
95+
* **Support for JWT (JSON Web Tokens)**, typically used by mobile or API-driven applications that require token-based authentication.
96+
97+
{% include 'alert/tutorial', content: 'For a deeper understanding of how authentication works in platformOS, explore the <a href="/developer-guide/users/authentication" target="_blank">Authentication</a> guide. It covers password-based login, temporary tokens, JWT tokens, session management, and more.' %}
98+
99+
{% render 'alert/next', content: 'Creating Navigation and Loading the Current Profile', url: '/get-started/user-management/create-navigation-current-profile' %}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
---
2+
metadata:
3+
title: Creating Navigation and Loading the Current Profile
4+
description: Learn how to add navigation links and dynamically show Login and Logout options using the User Module.
5+
converter: markdown
6+
---
7+
8+
A short recap: after installing the User Module, your instance automatically includes a full set of authentication pages such as Register, Log In, and Reset Password. These pages already work, but they are not linked from your application yet. To make them accessible for the users, you need to add navigation elements to your layout and adjust them so that they respond to the user’s login state.
9+
10+
In this chapter, we will:
11+
12+
* Add basic navigation links
13+
* Detect whether a user is logged in
14+
* Display Login or Logout accordingly
15+
* Display a simple greeting for authenticated users
16+
* Learn how the `current_profile` helper works and provides user information
17+
18+
By the end of this chapter, your application will feel functional and interactive, reacting to whether the user is authenticated.
19+
20+
## Step 1: Add Basic Navigation Links
21+
22+
Open your main layout at: `app/views/layouts/application.liquid`
23+
24+
Add a simple navigation block just above `{% raw %}{{ content_for_layout }}{% endraw %}`:
25+
26+
#### app/views/layouts/application.liquid
27+
28+
{% raw %}
29+
30+
```liquid
31+
<nav>
32+
<ul>
33+
<li><a href="/">Home</a></li>
34+
<li><a href="/sessions/new">Log in</a></li>
35+
</ul>
36+
</nav>
37+
38+
{{ content_for_layout }}
39+
```
40+
{% endraw %}
41+
42+
This contains a Home and a Log in link. Save the your file and reload your instance. The navigation should appear at the top of the page:
43+
44+
<img loading="lazy" src="{{ 'images/get-started/user-management/basic-navigation.png' | asset_url }}" alt="Basic navigation with Home and Login links">
45+
46+
If you click *Log in*, nothing will change. Since you are already authenticated, the User Module redirects you back to the homepage. At this stage, the navigation is still static, so the links do not reflect the user’s actual state.
47+
48+
In the next step, you will make the navigation dynamic so it displays the correct options for both guests and signed-in users.
49+
50+
## Step 2: Loading the Current Profile in the Layout
51+
52+
To conditionally show the correct navigation links, the layout needs access to the current user’s **profile**. The User Module provides a helper function designed specifically for this purpose. The following snippet comes from the **[Current Profile in Layouts](https://github.com/Platform-OS/pos-module-user?tab=readme-ov-file#current-profile-in-layouts)** section of the User Module documentation:
53+
54+
{% raw %}
55+
56+
```liquid
57+
{% liquid
58+
if context.current_user
59+
assign current_profile = context.exports.current_profile
60+
unless current_profile
61+
function current_profile = 'modules/user/helpers/current_profile'
62+
endunless
63+
endif
64+
%}
65+
```
66+
{% endraw %}
67+
68+
Add this block above your navigation in `app/views/layouts/application.liquid`.
69+
70+
### What this code does
71+
72+
1. It checks whether `context.current_user` exists
73+
2. If it does, it loads the full user profile using the `current-profile` helper
74+
3. Makes it available as `current_profile` in your layout
75+
76+
With this helper, you can show different navigation items depending on whether the user is authenticated.
77+
78+
## Step 3: Using the `current_profile` helper
79+
80+
Now that the authenticated user’s profile is available in the layout, you can update the navigation so that it responds to the login state.
81+
82+
In this step, you will replace your existing `<nav>` block with a version that:
83+
84+
* Shows a **Login** link when no user is logged in
85+
* Displays a **Welcome message with the user's email** and a **Logout** button when the user is authenticated
86+
87+
### Update your navigation block
88+
89+
Replace your earlier`<nav>` block with the following version, which displays different options for authenticated and unauthenticated users:
90+
91+
#### app/views/layouts/application.liquid
92+
93+
{% raw %}
94+
```liquid
95+
<nav>
96+
<a href="/">Home</a>
97+
<ul>
98+
{% if current_profile %}
99+
<li>Welcome, {{ current_profile.email }}</li>
100+
<form method="post" action="/sessions">
101+
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
102+
<input type="hidden" name="_method" value="delete">
103+
<button class="pos-button" type="submit">Logout</button>
104+
</form>
105+
106+
{% else %}
107+
<li><a href="/sessions/new">Login</a></li>
108+
{% endif %}
109+
</ul>
110+
</nav>
111+
```
112+
{% endraw %}
113+
114+
### Full `application.liquid` Example
115+
116+
Your entire layout file should now look similar to the following:
117+
118+
#### app/views/layouts/application.liquid
119+
120+
{% raw %}
121+
```liquid
122+
<!DOCTYPE html>
123+
<html class="pos-app">
124+
<head>
125+
126+
<meta charset="utf-8">
127+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
128+
<meta content="authenticity_token" name="csrf-param">
129+
<meta content="{{ form_authenticity_token }}" name="csrf-token">
130+
{% comment %} default platformOS provided styles {% endcomment %}
131+
{% render 'modules/common-styling/init' %}
132+
<link rel="stylesheet" href="{{ 'modules/user/style/pos-user-form.css' | asset_url }}">
133+
{% comment %} custom styles specific for this app, some overwrite the defaults {% endcomment %}
134+
<link rel="stylesheet" href="{{ 'style/app.css' | asset_url }}">
135+
136+
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes">
137+
138+
<script>
139+
/* namespace */
140+
if(typeof window.pos !== 'object'){
141+
window.pos = {};
142+
window.pos.modules = {};
143+
window.pos.translations = {};
144+
}
145+
</script>
146+
147+
<title>Example User Management Application | platformOS</title>
148+
149+
<link rel="stylesheet" href="{{ 'styles/app.css' | asset_url }}">
150+
</head>
151+
152+
<body>
153+
{% liquid
154+
if context.current_user
155+
assign current_profile = context.exports.current_profile
156+
unless current_profile
157+
function current_profile = 'modules/user/helpers/current_profile'
158+
endunless
159+
endif
160+
%}
161+
<nav>
162+
<a href="/">Home</a>
163+
<ul>
164+
165+
{% if current_profile %}
166+
<li>Welcome, {{ current_profile.email }}</li>
167+
<form method="post" action="/sessions">
168+
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
169+
<input type="hidden" name="_method" value="delete">
170+
<button class="pos-button" type="submit">Logout</button>
171+
</form>
172+
{% else %}
173+
<li><a href="/sessions/new">Login</a></li>
174+
{% endif %}
175+
176+
</ul>
177+
</nav>
178+
179+
{{ content_for_layout }}
180+
181+
{% liquid
182+
render 'modules/common-styling/toasts'
183+
%}
184+
185+
<script src="{{ 'scripts/app.js' | asset_url }}"></script>
186+
187+
</body>
188+
</html>
189+
```
190+
{% endraw %}
191+
192+
Let's continue with testing the log in and log out flow.
193+
194+
{% render 'alert/next', content: 'Testing the Login and Logout Flow', url: '/get-started/user-management/test-log-in-log-out' %}

0 commit comments

Comments
 (0)