🐣
actix-identityでログイン機能を実装する
ACTIXを使用したWeb開発において、actix-identityとactix-sessionを組み合わせてログイン機能を書いてみる。
ソースコード
最低限の機能のみ実装しているため、実際の開発ではユーザー認証や、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 = ¶ms.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