Session Authentication
@zeltjs/auth-session は、サーバーレンダリングアプリケーションのためのcookieベースのセッション管理を提供します。
インストール
pnpm add @zeltjs/auth-session @zeltjs/kv
クイックスタート
1. secretを設定する
SESSION_SECRET 環境変数を設定します。
# .env
SESSION_SECRET=your-secret-key-at-least-32-characters
2. セッションストアを設定する
セッションはKVストアに保存されます。デフォルトでは SessionConfig は session: namespace配下でin-memory adaptorを使用します。namespace(やその他のオプション)をカスタマイズするには SessionConfig を継承します。
@Config
class MySessionConfig extends SessionConfig {
override readonly kvStoreNamespace = 'sessions:';
}
3. middlewareを登録する
const app = createApp([http({
controllers: [AuthController, UserController],
middlewares: [SessionMiddleware],
})], { configs: [MySessionConfig] });
4. セッションを管理する
ハンドラー内でセッション関数を使用します。
@Controller('/auth')
class AuthController {
@Post('/login')
async login(req = request(LoginSchema)) {
const body = await req.body();
const user = await validateCredentials(body.email, body.password);
if (!user) {
throw new HTTPException(401, { message: 'Invalid credentials' });
}
setSession({ userId: user.id, name: user.name });
return { success: true };
}
@Get('/me')
me() {
const session = getSession();
if (!session) {
throw new HTTPException(401, { message: 'Not logged in' });
}
return session;
}
@Post('/logout')
logout() {
destroySession();
return { success: true };
}
}
Session API
| 関数 | 説明 |
|---|---|
getSession() | 現在のセッションデータを取得する(未ログインなら undefined) |
setSession(data) | セッションデータを設定する(既存のものを置き換える) |
updateSession(fn) | 関数でセッションデータを更新する |
destroySession() | セッションを破棄しcookieをクリアする |
isNewSession() | 新規に作成されたセッションかを確認する |
getSessionId() | 現在のセッションIDを取得する |
setSession
セッションを作成または置き換えます。
setSession({
userId: '123',
name: 'Alice',
cart: [{ productId: 'abc', qty: 2 }],
});
updateSession
セッションを部分的に更新します。
updateSession((session) => ({
...session,
lastActivity: Date.now(),
}));
destroySession
セッションとcookieをクリアします(ログアウト用)。
destroySession();
型安全なセッション
SessionSchema を継承すると、型安全にセッションへアクセスできます。
declare module '@zeltjs/auth-session' {
interface SessionSchema {
userId?: string;
name?: string;
email?: string;
cart?: CartItem[];
}
}
これで、すべてのセッション関数に型が付きます。
const session = getSession();
// TypeScriptはsession?.userId、session?.name、session?.cartを認識する
setSession({ userId: '123', name: 'Alice' });
// SessionSchemaに対して型チェックされる
設定
SessionConfig を継承して動作をカスタマイズします。
@Config
class MySessionConfig extends SessionConfig {
override readonly kvStoreNamespace = 'sessions:';
override get cookieName(): string {
return 'my_session'; // デフォルト: 'session'
}
override get ttlSec(): number {
return 86400 * 7; // 7日(デフォルト: 1日)
}
override get cookieOptions() {
return {
httpOnly: true,
secure: true,
sameSite: 'Strict' as const,
path: '/',
};
}
}
Configuration Options
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
kv | KVAdaptor | MemoryKV | セッションストレージの裏側となるKV adaptor(コンストラクタ引数2) |
kvStoreNamespace | string | 'session:' | セッションkeyのnamespace prefix |
secret | string | env.getString('SESSION_SECRET') | セッションID署名用のsecret |
cookieName | string | 'session' | cookie名 |
ttlSec | number | 86400(1日) | セッションのTTL(秒) |
cookieOptions | object | 下記参照 | cookieの設定 |
デフォルトのcookieオプション
override get cookieOptions() {
return {
httpOnly: true,
secure: this.env.getString('NODE_ENV', '') === 'production',
sameSite: 'Lax' as const,
path: '/',
};
}
}
ストレージバックエンド
Memory(開発用)
@Config
class MySessionConfig extends SessionConfig {
constructor(kv = inject(MemoryKV)) {
super(undefined, kv);
}
}
Redis(本番用)
SessionConfig はKV adaptorをコンストラクタの第2引数として受け取ります。セッションをRedisに保存するには RedisKVAdaptor を super() に渡します(第1引数はデフォルトの Env injectionを維持するため undefined のままにします)。
@Config
class MySessionConfig extends SessionConfig {
constructor(kv = inject(RedisKVAdaptor)) {
super(undefined, kv);
}
override readonly kvStoreNamespace = 'sessions:';
}
Redisを使用するには、adaptorが接続を解決できるよう(@zeltjs/redis の)RedisConfig を登録する必要があります。
User Contextとの統合
セッションは自動的にuser contextを設定しません。両者をつなぐmiddlewareを追加します。
@Middleware
export class SessionAuthMiddleware {
constructor(private userRepo = inject(UserRepository)) {}
async use(next: Next): Promise<Response | undefined> {
const session = getSession() as { userId?: string } | undefined;
if (session?.userId) {
const user = await this.userRepo.findById(session.userId);
setUser(
{ id: user.id, name: user.name, email: user.email },
user.roles
);
}
await next();
return undefined;
}
}
SessionMiddleware の後に登録します。
const app = createApp([http({
controllers: [UserController],
middlewares: [SessionMiddleware, SessionAuthMiddleware],
})], { configs: [MySessionConfig] });
セキュリティ上の注意点
CSRF Protection
セッションベースの認証にはCSRF対策が必要です。以下の方法を検討してください。
SameSite=Strictcookie(最も強力だが、UXに影響する場合がある)- 変更を伴う操作に対する
SameSite=Laxcookie + CSRFトークン - Double-submit cookieパターン
Session Fixation
ログイン後は必ずセッションIDを再生成してください。
@Controller('/auth')
class AuthController {
@Post('/login')
async login(req = request(LoginSchema)) {
const body = await req.body();
const user = await validateCredentials(body.email, body.password);
destroySession(); // 古いセッションをクリア
setSession({ userId: user.id, name: user.name }); // 新しいIDを作成
return { success: true };
}
}
Secure Cookies
本番環境では、必ずsecure cookieを使用してください。
const cookieOptions = {
httpOnly: true,
secure: true, // HTTPSのみ
sameSite: 'Strict' as const,
path: '/',
};