Skip to main content

plain.passwords

Password hashing, validation, and authentication views for Plain.

Overview

You can hash and verify passwords using the hash_password and check_password functions:

from plain.passwords.hashers import hash_password, check_password

# Hash a password for storage
hashed = hash_password("my-secret-password")
# Returns something like: pbkdf2_sha256$720000$abc123...$xyz789...

# Verify a password against a hash
is_valid = check_password("my-secret-password", hashed)
# Returns True

For user authentication, you can use the built-in views. Add PasswordLoginView to your URLs:

# app/urls.py
from plain.urls import path
from plain.passwords.views import PasswordLoginView

urlpatterns = [
    path("login/", PasswordLoginView, name="login"),
]

Password hashing

Passwords are hashed using PBKDF2 with SHA256 by default. The hash_password function generates a secure hash:

from plain.passwords.hashers import hash_password

hashed = hash_password("user-password")

The check_password function verifies a password against a stored hash. It also handles automatic hash upgrades when the hashing algorithm changes:

from plain.passwords.hashers import check_password


def setter(new_hash):
    # Called when the hash needs to be upgraded
    user.password = new_hash
    user.update()


is_valid = check_password("user-password", stored_hash, setter=setter)

You can configure which hashers are available via the PASSWORD_HASHERS setting. The first hasher in the list is used for new passwords:

# app/settings.py
PASSWORD_HASHERS = [
    "plain.passwords.hashers.PBKDF2PasswordHasher",
]

To create a custom hasher, subclass BasePasswordHasher and implement the required methods.

Password validation

Three validators are included for checking password strength:

from plain.passwords.validators import (
    MinimumLengthValidator,
    CommonPasswordValidator,
    NumericPasswordValidator,
)
from plain.exceptions import ValidationError

validators = [
    MinimumLengthValidator(min_length=10),
    CommonPasswordValidator(),
    NumericPasswordValidator(),
]

password = "test"
for validator in validators:
    try:
        validator(password)
    except ValidationError as e:
        print(e.message)

PasswordField

PasswordField is a model field that automatically hashes passwords before saving. It includes all three validators by default:

from plain import postgres
from plain.postgres import types
from plain.passwords.models import PasswordField


@postgres.register_model
class User(postgres.Model):
    email: str = types.EmailField()
    password = PasswordField()

    model_options = postgres.Options(
        constraints=[
            postgres.UniqueConstraint(fields=["email"], name="unique_email"),
        ],
    )

When you assign a raw password, it gets hashed automatically on save:

user = User(email="user@example.com", password="my-password")
user.create()
# user.password is now a hash like: pbkdf2_sha256$720000$...

For better type checking support, you can import from plain.passwords.types:

from plain.passwords.types import PasswordField

Views

All views are designed to work with plain.auth for session management.

Login

PasswordLoginView handles email/password authentication:

from plain.urls import path
from plain.passwords.views import PasswordLoginView

urlpatterns = [
    path("login/", PasswordLoginView, name="login"),
]

You can customize the success URL:

class MyLoginView(PasswordLoginView):
    success_url = "/dashboard/"

Signup

PasswordSignupView creates new users with email and password:

from plain.urls import path
from plain.passwords.views import PasswordSignupView

urlpatterns = [
    path("signup/", PasswordSignupView, name="signup"),
]

Password change

PasswordChangeView lets authenticated users change their password by entering their current password:

from plain.urls import path
from plain.passwords.views import PasswordChangeView

urlpatterns = [
    path("password/change/", PasswordChangeView, name="password_change"),
]

Password reset

Password reset requires two views and an email template. PasswordForgotView sends the reset email, and PasswordResetView handles the token and new password:

from plain.urls import path
from plain.passwords.views import PasswordForgotView, PasswordResetView


class MyPasswordForgotView(PasswordForgotView):
    reset_confirm_url_name = "password_reset"
    success_url = "/login/"


class MyPasswordResetView(PasswordResetView):
    success_url = "/login/"


urlpatterns = [
    path("password/forgot/", MyPasswordForgotView, name="password_forgot"),
    path("password/reset/", MyPasswordResetView, name="password_reset"),
]

You need to create a password_reset email template for plain.email. The template receives email, user, and url in its context.

Forms

Several forms are available for building custom authentication flows:

Settings

Setting Default Env var
PASSWORD_HASHERS [...] PLAIN_PASSWORD_HASHERS (JSON)

See default_settings.py for more details.

FAQs

How do I customize the login form?

Subclass PasswordLoginForm and set form_class on your view:

from plain.passwords.forms import PasswordLoginForm
from plain.passwords.views import PasswordLoginView


class MyLoginForm(PasswordLoginForm):
    # Add custom fields or validation
    pass


class MyLoginView(PasswordLoginView):
    form_class = MyLoginForm

How do I customize password validation?

Pass custom validators to PasswordField:

from plain.passwords.models import PasswordField
from plain.passwords.validators import MinimumLengthValidator

password = PasswordField(
    validators=[
        MinimumLengthValidator(min_length=12),
    ]
)

How do I use a different hashing algorithm?

Add your hasher to PASSWORD_HASHERS. The first one is used for new passwords:

PASSWORD_HASHERS = [
    "myapp.hashers.Argon2PasswordHasher",
    "plain.passwords.hashers.PBKDF2PasswordHasher",  # For existing passwords
]

How long are password reset tokens valid?

By default, tokens expire after 1 hour. Override reset_token_max_age on PasswordResetView to change this:

class MyPasswordResetView(PasswordResetView):
    reset_token_max_age = 60 * 60 * 24  # 24 hours

Installation

Install the package from PyPI:

uv add plain.passwords

Add the password field to your User model:

# app/models.py
from plain import postgres
from plain.postgres import types
from plain.passwords.models import PasswordField


@postgres.register_model
class User(postgres.Model):
    email: str = types.EmailField()
    password = PasswordField()

    model_options = postgres.Options(
        constraints=[
            postgres.UniqueConstraint(fields=["email"], name="unique_email"),
        ],
    )

Add login and logout views to your URLs:

# app/urls.py
from plain.urls import path
from plain.auth.views import LogoutView
from plain.passwords.views import PasswordLoginView

urlpatterns = [
    path("login/", PasswordLoginView, name="login"),
    path("logout/", LogoutView, name="logout"),
]

Create templates for your views. For the login view, create templates/passwords/passwordlogin.html:

{% extends "base.html" %}

{% block content %}
<form method="post">
    <div>
        <label for="{{ form.email.html_id }}">Email</label>
        <input
            type="email"
            name="{{ form.email.html_name }}"
            id="{{ form.email.html_id }}"
            value="{{ form.email.value }}"
        >
        {% for error in form.email.errors %}
        <p>{{ error }}</p>
        {% endfor %}
    </div>

    <div>
        <label for="{{ form.password.html_id }}">Password</label>
        <input
            type="password"
            name="{{ form.password.html_name }}"
            id="{{ form.password.html_id }}"
        >
        {% for error in form.password.errors %}
        <p>{{ error }}</p>
        {% endfor %}
    </div>

    <button type="submit">Log in</button>
</form>
{% endblock %}

For password resets, install plain.email and create a reset email template.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plain_passwords-0.26.7.tar.gz (107.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plain_passwords-0.26.7-py3-none-any.whl (108.0 kB view details)

Uploaded Python 3

File details

Details for the file plain_passwords-0.26.7.tar.gz.

File metadata

  • Download URL: plain_passwords-0.26.7.tar.gz
  • Upload date:
  • Size: 107.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_passwords-0.26.7.tar.gz
Algorithm Hash digest
SHA256 e09dc93d1d44db9f615658aec515362d7b29799677455206e2674cad988ea9c2
MD5 d492e39beb4486aec9311be8118f9351
BLAKE2b-256 e305c309c4f0a3cc026d618581c2a47b6a9b5b5cfe2c759124609db411f06171

See more details on using hashes here.

File details

Details for the file plain_passwords-0.26.7-py3-none-any.whl.

File metadata

  • Download URL: plain_passwords-0.26.7-py3-none-any.whl
  • Upload date:
  • Size: 108.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_passwords-0.26.7-py3-none-any.whl
Algorithm Hash digest
SHA256 9d9c61947b23b1e2ad8f1b12ffc16580b2c04f7b4850c0c6a440fae0d0bf6620
MD5 64a81887bf6e2b91e46d235265c3fa02
BLAKE2b-256 c92711e0a2cf611f8cb38297730e63894374e097d30a6ee773b34df9a1dffe4e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page