🔖

actix-identityでログイン機能を実装する

2022/09/29に公開

ACTIXを使用したWeb開発において、actix-identityactix-sessionを組み合わせてログイン機能を書いてみる。

sample

ソースコード

最低限の機能のみ実装しているため、実際の開発ではユーザー認証や、actix-sessionのバックエンドはRedis-basedを選ぶ等の必要があるかと思います。

Cargo.toml
[package]
name = "actix_login_example"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "4"
actix-identity = "0.5"
actix-session = { version = "0.7", features = [ "cookie-session" ] }
askama = "0.11"
serde = "1.0"
main.rs
mod auth;

use actix_web::{cookie::Key, web, App, HttpResponse, HttpServer, Responder};
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
use askama::Template;

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    username: String,
}

async fn index(user: Option<Identity>) -> impl Responder {
    let html = IndexTemplate {
        username: if let Some(user) = user {
            user.id().unwrap()
        } else {
            "".to_string()
        }
    };
    let view = html.render().expect("failed to render html");
    HttpResponse::Ok()
        .content_type("text/html")
        .body(view)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let secret_key = Key::generate();

    HttpServer::new(move || {
        App::new()
            .wrap(IdentityMiddleware::default())
            .wrap(SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
                .cookie_secure(false)
                .build())
            .route("/", web::get().to(index))
            .route("/login", web::post().to(auth::login))
            .route("/logout", web::post().to(auth::logout))
    })
        .bind("localhost:8080")?
        .run()
        .await
}
auth.rs
use actix_web::{http::header, web, HttpMessage, HttpRequest, HttpResponse, Responder};
use actix_identity::Identity;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct LoginParams {
    username: String,
}

pub async fn login(request: HttpRequest, params: web::Form<LoginParams>) -> impl Responder {
    let username = &params.username;
    Identity::login(&request.extensions(), username.into()).unwrap();
    HttpResponse::SeeOther().append_header((header::LOCATION, "/")).finish()
}

pub async fn logout(user: Option<Identity>) -> impl Responder {
    if let Some(user) = user {
        user.logout();
    }
    HttpResponse::SeeOther().append_header((header::LOCATION, "/")).finish()
}
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
</head>
<body>
    {% if username.is_empty() %}
    <form action="/login" method="POST">
        <input type="text" name="username" required>
        <input type="submit" id="confirm" value="Login">
    </form>
    {% else %}
    <p>Hi, {{ username }}</p>
    <form action="/logout" method="POST">
        <input type="submit" id="confirm" value="Logout">
    </form>
    {% endif %}
</body>
</html>

Discussion