Skip to content

Tokens

A Keysmith token is a database record representing a machine credential. The raw secret lives only in the client's hands - Keysmith stores a hash.


Creating tokens

Always use create_token(). It handles prefix generation, hashing, scope validation, and audit logging.

from datetime import timedelta
from django.utils import timezone
from keysmith.services.tokens import create_token

token, raw_token = create_token(
    name="billing-worker",
    description="Nightly billing job",
    user=service_user,
    created_by=admin_user,
    expires_at=timezone.now() + timedelta(days=90),
    request=request,  # optional - enriches audit context
)
Argument Required Notes
name Max 128 characters
description Free text
user Linked user for request identity
created_by Who issued the token
scopes Permission instances; uses DEFAULT_SCOPES when omitted
expires_at Defaults to now + DEFAULT_EXPIRY_DAYS
token_type "user" (default) or "system"
request Adds path/IP/user-agent to audit event

Returns (token, raw_token). Store raw_token immediately.


Lifecycle operations

stateDiagram-v2
    [*] --> Active: create_token()
    Active --> Active: rotate_token()
    Active --> Revoked: revoke_token()
    Active --> Purged: purge_token()
    Active --> Expired: past expires_at
    Revoked --> [*]
    Purged --> [*]
    Expired --> [*]

Rotate

Replaces the secret. Same prefix, scopes, and metadata. Old raw token invalidated immediately.

from keysmith.services.tokens import rotate_token

new_raw = rotate_token(token, actor=request.user, request=request)

Raises ValueError if the token is revoked or purged.

Revoke

Marks revoked=True. Token cannot authenticate.

from keysmith.services.tokens import revoke_token

revoke_token(token, actor=request.user, request=request)

Idempotent - calling twice is safe. The purge=True keyword delegates to purge_token() for backward compatibility.

Purge

Soft-delete: sets both purged=True and revoked=True.

from keysmith.services.tokens import purge_token

purge_token(token, actor=request.user, request=request)

Use purge when workflows need to distinguish permanent retirement from a simple disable.


Querying tokens

from django.utils import timezone
from keysmith.models import Token

Token.objects.filter(revoked=False, purged=False)                          # active
Token.objects.filter(expires_at__lt=timezone.now(), revoked=False)         # expired
Token.objects.filter(user=service_user).order_by("-created_at")            # by owner

Model helpers:

Property / method Meaning
token.is_expired expires_at is in the past
token.is_active Not revoked, not purged, not expired
token.can_authenticate() Same as is_active - required on custom models
token.mark_used() Updates last_used_at (called automatically on auth)

Best practices

One token per integration

Issue separate tokens for each service or client. Limits blast radius and simplifies rotation.

Pass request context

Include request and actor in lifecycle calls so audit logs capture who did what and from where.

Rotate on exposure

Don't wait for a scheduled rotation window if a secret may have leaked.


See also: Admin · Services reference