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