microdot.login — user login flow

A higher-level wrapper around microdot.session that implements the conventional username / password login flow: a user loader callback maps the session’s stored user-id back to the application’s user object, route decorators redirect unauthenticated requests to a configurable login URL, and optional “remember me” cookies let returning visitors stay signed in across browser sessions.

Requires microdot.session to be initialized on the application before the login object is constructed – the session is where the user-id is stored.

class Login

class microdot.login.Login(login_url: str = '/login')
login_url

URL the decorator redirects unauthenticated requests to. The application provides the actual login form/handler at this route. Default '/login'.

user_loader(f)

Decorator that registers the user-resolver callback. f takes the user-id stored in the session and returns the user object (or None if the id is no longer valid – a deleted account, a revoked session, etc.).

login = Login()

@login.user_loader
async def load_user(user_id):
    return users.get(user_id)
__call__(f)

Decorating a route with the Login instance gates it behind authentication:

@app.get('/dashboard')
@login
async def dashboard(request):
    user = request.g.current_user
    # ...

Unauthenticated requests are redirected to login_url with a ?next=<original-url> query parameter so the login handler can send the user back where they came from.

fresh(f)

Like __call__(), but rejects sessions restored from a “remember me” cookie – the user must have logged in explicitly since the last full login. Used to gate sensitive routes (password change, account deletion) so a stolen remember-me cookie cannot reach them.

async login_user(request, user, remember: bool | int = False, redirect_url: str = '/')

Mark user as logged in for request. Stores the user id in the session and returns a redirect response.

user

The user object returned by the user loader. Must have an id attribute.

remember

If truthy, also set a long-lived _remember cookie. Pass True for the default 30 days, or an integer for the number of days the cookie should last.

redirect_url

Where to redirect after login. Overridden by ?next=<url> from the original request if it points to a same-site path.

Returns the redirect response – return its value from the login route handler.

async logout_user(request)

Clear the user id from the session and remove any _remember cookie. Use from a /logout route:

@app.post('/logout')
async def logout(request):
    await login.logout_user(request)
    return redirect('/')
async get_current_user(request)

Return the currently logged-in user object (or None). Memoized on request.g.current_user so repeated calls within one request hit the loader only once.

Example

A minimal login flow:

from microdot import Microdot, redirect
from microdot.session import Session
from microdot.login import Login

app = Microdot()
Session(app, secret_key=load_secret())
login = Login()

@login.user_loader
async def load_user(user_id):
    return users.get(user_id)

@app.get('/login')
async def login_page(request):
    return Response.send_file('static/login.html')

@app.post('/login')
async def do_login(request):
    user = authenticate(request.form['user'], request.form['pass'])
    if not user:
        return redirect('/login?error=1')
    return await login.login_user(request, user, remember=True)

@app.get('/dashboard')
@login
async def dashboard(request):
    return 'hi ' + request.g.current_user.name

@app.post('/logout')
async def logout(request):
    await login.logout_user(request)
    return redirect('/')

The _remember cookie is itself a signed JWT (using the session’s secret), so a stolen cookie cannot be reused on a different application or after the secret has been rotated.