1# plain.auth
2
3**Add users to your app and decide what they can access.**
4
5- [Overview](#overview)
6- [Authentication setup](#authentication-setup)
7 - [Settings configuration](#settings-configuration)
8 - [Creating a user model](#creating-a-user-model)
9 - [Login views](#login-views)
10- [Checking if a user is logged in](#checking-if-a-user-is-logged-in)
11- [Restricting views](#restricting-views)
12- [Testing with authenticated users](#testing-with-authenticated-users)
13- [FAQs](#faqs)
14- [Installation](#installation)
15
16## Overview
17
18The `plain.auth` package handles user authentication and authorization for Plain applications. You can check if a user is logged in like this:
19
20```python
21from plain.auth import get_request_user
22
23user = get_request_user(request)
24if user:
25 print(f"Hello, {user.email}!")
26else:
27 print("You are not logged in.")
28```
29
30You can restrict a view to logged-in users using [`AuthViewMixin`](./views.py#AuthViewMixin):
31
32```python
33from plain.auth.views import AuthViewMixin
34from plain.views import View
35
36class ProfileView(AuthViewMixin, View):
37 login_required = True
38
39 def get(self):
40 return f"Welcome, {self.user.email}!"
41```
42
43## Authentication setup
44
45### Settings configuration
46
47Configure your authentication settings in `app/settings.py`:
48
49```python
50INSTALLED_PACKAGES = [
51 # ...
52 "plain.auth",
53 "plain.sessions",
54 "plain.passwords", # Or another auth method
55]
56
57MIDDLEWARE = [
58 "plain.sessions.middleware.SessionMiddleware",
59]
60
61AUTH_USER_MODEL = "users.User"
62AUTH_LOGIN_URL = "login"
63```
64
65### Creating a user model
66
67You can create your own user model using `plain create users` or manually:
68
69```python
70# app/users/models.py
71from plain import models
72from plain.passwords.models import PasswordField
73
74
75class User(models.Model):
76 email = models.EmailField()
77 password = PasswordField()
78 is_admin = models.BooleanField(default=False)
79 created_at = models.DateTimeField(auto_now_add=True)
80
81 def __str__(self):
82 return self.email
83```
84
85### Login views
86
87To log users in, you need to pair this package with an authentication method:
88
89- [plain.passwords](../../plain-passwords/plain/passwords/README.md) - Username/password authentication
90- [plain.oauth](../../plain-oauth/plain/oauth/README.md) - OAuth provider authentication
91- [plain.loginlink](../../plain-loginlink/plain/loginlink/README.md) - Magic link authentication
92
93Here's an example with password authentication:
94
95```python
96# app/urls.py
97from plain.auth.views import LogoutView
98from plain.urls import path
99from plain.passwords.views import PasswordLoginView
100
101
102class LoginView(PasswordLoginView):
103 template_name = "login.html"
104
105
106urlpatterns = [
107 path("logout/", LogoutView, name="logout"),
108 path("login/", LoginView, name="login"),
109]
110```
111
112## Checking if a user is logged in
113
114In templates, you can use the `get_current_user()` function:
115
116```html
117{% if get_current_user() %}
118 <p>Hello, {{ get_current_user().email }}!</p>
119{% else %}
120 <p>You are not logged in.</p>
121{% endif %}
122```
123
124In Python code, use [`get_request_user()`](./requests.py#get_request_user):
125
126```python
127from plain.auth import get_request_user
128
129user = get_request_user(request)
130if user:
131 print(f"Hello, {user.email}!")
132else:
133 print("You are not logged in.")
134```
135
136## Restricting views
137
138You can use [`AuthViewMixin`](./views.py#AuthViewMixin) to restrict views to logged-in users, admin users, or custom logic:
139
140```python
141from plain.auth.views import AuthViewMixin
142from plain.http import ForbiddenError403
143from plain.views import View
144
145
146class LoggedInView(AuthViewMixin, View):
147 login_required = True
148
149
150class AdminOnlyView(AuthViewMixin, View):
151 login_required = True
152 admin_required = True
153
154
155class CustomPermissionView(AuthViewMixin, View):
156 def check_auth(self):
157 super().check_auth()
158 if not self.user.is_special:
159 raise ForbiddenError403("You're not special!")
160```
161
162The [`AuthViewMixin`](./views.py#AuthViewMixin) provides:
163
164- `login_required` - Requires a logged-in user
165- `admin_required` - Requires `user.is_admin` to be True
166- `check_auth()` - Override for custom authorization logic
167
168## Testing with authenticated users
169
170When writing tests, you can use [`login_client()`](./test.py#login_client) to simulate an authenticated user:
171
172```python
173from plain.auth.test import login_client
174from plain.test import Client
175
176from app.users.models import User
177
178
179def test_profile_view():
180 user = User.objects.create(email="[email protected]")
181 client = Client()
182 login_client(client, user)
183
184 response = client.get("/profile/")
185 assert response.status_code == 200
186```
187
188You can also log out a test user with [`logout_client()`](./test.py#logout_client):
189
190```python
191from plain.auth.test import login_client, logout_client
192
193# ... after logging in
194logout_client(client)
195```
196
197## FAQs
198
199#### How do I log in a user programmatically?
200
201You can use the [`login()`](./sessions.py#login) function to log in a user:
202
203```python
204from plain.auth.sessions import login
205
206login(request, user)
207```
208
209#### How do I log out a user programmatically?
210
211You can use the [`logout()`](./sessions.py#logout) function:
212
213```python
214from plain.auth.sessions import logout
215
216logout(request)
217```
218
219#### How do I invalidate sessions when a user changes their password?
220
221By default, if you have [plain.passwords](../../plain-passwords/plain/passwords/README.md) installed, sessions are automatically invalidated when the `password` field changes. This is controlled by the `AUTH_USER_SESSION_HASH_FIELD` setting. You can change this to a different field name, or set it to an empty string to disable this feature.
222
223#### How do I get the user model class?
224
225You can use the [`get_user_model()`](./sessions.py#get_user_model) function:
226
227```python
228from plain.auth.sessions import get_user_model
229
230User = get_user_model()
231```
232
233## Installation
234
235Install the `plain.auth` package from [PyPI](https://pypi.org/project/plain.auth/):
236
237```bash
238uv add plain.auth
239```