10.10. Login untuk dashboard

Dashboard web memerlukan formulir login -- orang acak di LAN seharusnya tidak melihat halaman tersebut. Itulah yang dilakukan oleh sesi berbasis cookie dan dekorator login.

Sesi adalah dict kecil yang ditulis kamera ke dalam cookie. Cookie tersebut ditandatangani dengan rahasia penandatanganan JWT yang dimuat sebelumnya dalam bab ini, sehingga browser dapat membawanya tetapi tidak dapat memanipulasi isinya tanpa membatalkan tanda tangan.

10.10.1. Siapkan objek sesi dan login

microdot.session.Session memasang mekanisme sesi pada app. microdot.login.Login menambahkan dekorator bergaya login_required dan pembantu login_user / logout_user:

# auth/login.py
from microdot.session import Session
from microdot.login import Login

Session(app, secret_key=SECRET,
        cookie_options={'http_only': True, 'secure': False})
login = Login()

USERS = {
    'owner': {'id': 'owner', 'password_hash': load_password_hash()},
}

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

http_only=True mencegah JavaScript di halaman dari membaca cookie -- pertahanan berlapis terhadap penyerang XSS yang membajak sesi. secure=False adalah placeholder sampai HTTPS terpasang; ubah ke True setelah server berjalan melalui TLS agar cookie tidak pernah berjalan melalui HTTP biasa.

Catatan

Cross-site scripting (XSS) adalah kelas serangan di mana penyerang menjalankan JavaScript di dalam tampilan halaman tepercaya pengguna -- biasanya melalui field formulir yang tidak di-escape, komentar yang di-render HTML, atau widget pihak ketiga yang rentan. Flag cookie http_only tidak mencegah XSS; ia hanya menjaga agar cookie sesi tidak dapat diakses oleh skrip yang disuntikkan, sehingga XSS yang berhasil tidak dapat langsung diubah menjadi pembajakan sesi.

user_loader dipanggil pada setiap permintaan yang dilindungi untuk mengubah ID yang tersimpan dalam sesi menjadi rekaman pengguna yang akan dilihat oleh handler. Jaga agar tetap ringan -- ia berjalan di jalur kritis.

10.10.2. Formulir login, posting login, logout

Formulir login itu sendiri adalah halaman statis yang disajikan dari /sdcard/static/ sama seperti dashboard. Formulir tersebut melakukan POSTke /login:

from microdot import redirect

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

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

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

microdot.login.Login.login_user() menulis cookie sesi dan mengembalikan redirect 302 ke halaman yang awalnya ingin diakses oleh klien (argumen query next=, dengan fallback ke /). remember=True juga menulis cookie _remember berumur lebih panjang agar sesi bertahan setelah browser di-restart.

microdot.login.Login.logout_user() menghapus kedua cookie dan redirect lanjutan mengirimkan browser kembali ke formulir.

10.10.3. Melindungi dashboard

Dekorasi setiap rute yang menghadap dashboard dengan @login agar permintaan yang tidak terautentikasi mendapat redirect 302 ke /login alih-alih konten:

@app.get('/<path:filename>')
@login
async def static(request, filename):
    ...

@app.get('/config')
@login
async def get_config(request):
    ...

@app.post('/config')
@login
async def set_config(request):
    ...

@app.get('/events')
@login
@with_sse
async def events(request, sse):
    ...

@app.get('/control')
@login
@with_websocket
async def control(request, ws):
    ...

Endpoint API berbasis token (/api/login, /api/ack) tetap berbasis token -- mereka untuk aplikasi ponsel, bukan browser. Auth token dan auth sesi dapat berdampingan dengan baik pada app yang sama.

10.10.4. Sesi segar vs sesi yang diingat

Cookie _remember menjaga pengguna tetap login setelah browser di-restart, tetapi ini adalah bentuk auth yang lebih lemah -- browser mungkin ditinggalkan di kafe. Untuk rute yang mengubah kata sandi, mendaftarkan token API, atau melakukan hal lain yang layak untuk diautentikasi ulang, gunakan dekorator @login.fresh alih-alih @login. Login segar adalah saat pengguna mengetikkan kata sandi mereka dalam sesi ini; login yang diingat tidak demikian. Dashboard tidak memiliki sesuatu yang mencapai ambang batas itu, tetapi dekorator tersebut tersedia saat Anda membutuhkannya.

Dashboard sekarang memerlukan login sebelum salah satu rutenya merespons.