Skip to main content

Password authentication for Plain.

Project description

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.save()

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 models
from plain.passwords.models import PasswordField

@models.register_model
class User(models.Model):
    email = models.EmailField()
    password = PasswordField()

    model_options = models.Options(
        constraints=[
            models.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.save()
# 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 models
from plain.passwords.models import PasswordField

@models.register_model
class User(models.Model):
    email = models.EmailField()
    password = PasswordField()

    model_options = models.Options(
        constraints=[
            models.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.

Project details


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.23.3.tar.gz (102.4 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.23.3-py3-none-any.whl (105.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: plain_passwords-0.23.3.tar.gz
  • Upload date:
  • Size: 102.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","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.23.3.tar.gz
Algorithm Hash digest
SHA256 1a66452f9bce15fa444e8bd24d594ceaa8d723415ad30f98b96d2b70c08a2e83
MD5 aca877f22d258304040f2bd8ca9d10fe
BLAKE2b-256 6db836c8c48a07c33a7f5f02eba98fb6355859c7e79408d81ce6cfb0ab2eadcd

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plain_passwords-0.23.3-py3-none-any.whl
  • Upload date:
  • Size: 105.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","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.23.3-py3-none-any.whl
Algorithm Hash digest
SHA256 867bb5a5bd8c6bf5f684e40174bb44cea2ab4691ebe52af36f55b15373999b0e
MD5 fae7da0db6947f1acd18340d74619365
BLAKE2b-256 0cf3d741d9e1cab871fa46f74acbdacde5a4dc1ac24f98e996ac97c037194d8f

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 Pingdom Monitoring Sentry Error logging StatusPage Status page