microdot.login --- 使用者登入流程

圍繞 microdot.session 的較高階包裝,實作慣用的使用者名稱/密碼登入流程:使用者載入器(user loader)回呼函式會將工作階段所儲存的使用者 ID 映射回應用程式的使用者物件,路由裝飾器會將未經驗證的請求重新導向至可設定的登入 URL,而選用的「記住我」cookie 則讓回訪者跨瀏覽器工作階段保持登入狀態。

需要在建構登入物件之前於應用程式上初始化 microdot.session —— 工作階段即是儲存使用者 ID 的位置。

class Login

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

裝飾器將未經驗證的請求重新導向至的 URL。應用程式會在此路由提供實際的登入表單/處理常式。預設為 '/login'

user_loader(f)

註冊使用者解析回呼函式的裝飾器。f 接受儲存於工作階段中的使用者 ID 並回傳使用者物件(若該 ID 已不再有效則回傳 None —— 例如已刪除的帳號、已撤銷的工作階段等)。

login = Login()

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

Login 實例裝飾路由即可將其置於驗證之後::

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

未經驗證的請求會被重新導向至 login_url,並附帶 ?next=<original-url> 查詢參數,讓登入處理常式可將使用者送回其原本所在之處。

fresh(f)

__call__() 類似,但會拒絕從「記住我」cookie 還原的工作階段 —— 使用者必須自上次完整登入後曾明確登入。用於保護敏感路由(變更密碼、刪除帳號),使被竊取的記住我 cookie 無法觸及這些路由。

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

user 標記為已為 request 登入。會將使用者 ID 儲存於工作階段中並回傳重新導向回應。

user

使用者載入器所回傳的使用者物件。必須具有 id 屬性。

remember

若為真值,則同時設定長效的 _remember cookie。傳入 True 表示預設的 30 天,或傳入整數表示 cookie 應持續的天數。

redirect_url

登入後重新導向的目的地。若原始請求的 ?next=<url> 指向同站路徑,則由其覆寫。

回傳重新導向回應 —— 請從登入路由處理常式回傳其值。

async logout_user(request)

從工作階段清除使用者 ID 並移除任何 _remember cookie。請從 /logout 路由使用::

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

回傳目前已登入的使用者物件(或 None)。會記憶於 request.g.current_user 上,因此在單一請求內重複呼叫只會觸及載入器一次。

範例

一個最簡的登入流程::

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('/')

_remember cookie 本身是一個簽署過的 JWT(使用工作階段的密鑰),因此被竊取的 cookie 無法在不同的應用程式上、或在密鑰輪替後被重複使用。