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(unique=True)
    password = PasswordField()

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(unique=True)
    password = PasswordField()

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">
    {{ csrf_input }}
    {{ form.as_elements }}
    <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.2.tar.gz (101.9 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.2-py3-none-any.whl (105.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: plain_passwords-0.23.2.tar.gz
  • Upload date:
  • Size: 101.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","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.2.tar.gz
Algorithm Hash digest
SHA256 59cf1d7cf2ac4900b9896bada85313815c78f702c83b8cd53e3847a79ffed04a
MD5 04b937a1355540ba51d0c243316c8cfd
BLAKE2b-256 40f573dcf1ec84ba4271e65210ec1988dee8214a276016ace86e91a63bec0098

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plain_passwords-0.23.2-py3-none-any.whl
  • Upload date:
  • Size: 105.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3d147f43c1ce92fe0aa3e3863c5586feb0c9c3b8143bca74e7003bd07b94e262
MD5 bae529ec07c7c42aff916ff0ca903e62
BLAKE2b-256 3cf432e7766adaf7e8c076cbb47fdbfcc055a47c6bf90056ccfd237da0d2a2c6

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