1. セッション管理
npm install express-session @types/express-session connect-redis
// main.ts
import * as session from 'express-session';
import { createClient } from 'redis';
import RedisStore from 'connect-redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000, // 1 ngày
},
}));
// Sử dụng session trong controller
@Controller('auth')
export class AuthController {
@Post('login')
login(@Body() dto: LoginDto, @Session() session: Record<string, any>) {
const user = this.authService.validate(dto);
session.userId = user.id;
session.role = user.role;
return { message: 'Logged in' };
}
@Post('logout')
logout(@Session() session: Record<string, any>) {
session.destroy();
return { message: 'Logged out' };
}
}
2. Cookie ベースの認証
import { Response } from 'express';
@Post('login')
async login(@Body() dto: LoginDto, @Res({ passthrough: true }) res: Response) {
const { accessToken, refreshToken } = await this.authService.login(dto);
// Set access token trong HttpOnly cookie
res.cookie('access_token', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 15 * 60 * 1000, // 15 phút
});
// Refresh token trong separate cookie
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/auth/refresh', // Chỉ gửi khi refresh
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 ngày
});
return { message: 'Logged in' };
}
// JWT Strategy đọc từ cookie
@Injectable()
export class JwtCookieStrategy extends PassportStrategy(Strategy, 'jwt-cookie') {
constructor() {
super({
jwtFromRequest: (req: Request) => req.cookies?.access_token,
secretOrKey: process.env.JWT_ACCESS_SECRET,
});
}
validate(payload: JwtPayload) {
return { id: payload.sub, email: payload.email, role: payload.role };
}
}
3. OAuth2 — Google ログイン
npm install passport-google-oauth20
npm install -D @types/passport-google-oauth20
// auth/strategies/google.strategy.ts
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private config: ConfigService) {
super({
clientID: config.get('GOOGLE_CLIENT_ID'),
clientSecret: config.get('GOOGLE_CLIENT_SECRET'),
callbackURL: config.get('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
) {
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
name: `${name.givenName} ${name.familyName}`,
avatar: photos[0]?.value,
provider: 'google',
providerId: profile.id,
};
done(null, user);
}
}
// auth/auth.controller.ts
@Controller('auth')
export class AuthController {
@Get('google')
@UseGuards(AuthGuard('google'))
googleLogin() {} // Redirect tới Google
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleCallback(@Req() req, @Res() res: Response) {
const tokens = await this.authService.socialLogin(req.user);
// Redirect về frontend với token
res.redirect(
`${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`
);
}
}
4. OAuth2 — GitHub ログイン
npm install passport-github2
@Injectable()
export class GithubStrategy extends PassportStrategy(Strategy, 'github') {
constructor(private config: ConfigService) {
super({
clientID: config.get('GITHUB_CLIENT_ID'),
clientSecret: config.get('GITHUB_CLIENT_SECRET'),
callbackURL: config.get('GITHUB_CALLBACK_URL'),
scope: ['user:email'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any) {
return {
email: profile.emails?.[0]?.value,
name: profile.displayName || profile.username,
avatar: profile.photos?.[0]?.value,
provider: 'github',
providerId: profile.id.toString(),
};
}
}
5. ソーシャル ログイン サービス — アカウントのリンク
@Injectable()
export class AuthService {
async socialLogin(socialUser: SocialUser) {
// 1. Tìm user theo provider + providerId
let user = await this.usersService.findByProvider(
socialUser.provider,
socialUser.providerId,
);
if (!user) {
// 2. Tìm theo email (account linking)
user = await this.usersService.findByEmail(socialUser.email);
if (user) {
// Link social account vào user hiện có
await this.usersService.linkSocialAccount(user.id, {
provider: socialUser.provider,
providerId: socialUser.providerId,
});
} else {
// 3. Tạo user mới
user = await this.usersService.create({
email: socialUser.email,
name: socialUser.name,
avatar: socialUser.avatar,
provider: socialUser.provider,
providerId: socialUser.providerId,
});
}
}
return this.generateTokens(user);
}
}
6. まとめ
- セッション: サーバー側の状態、本番環境に Redis ストアを使用
- クッキー: HttpOnly + Secure + SameSite による安全なトークン ストレージ
- OAuth2: Google、GitHub、Facebookによるソーシャルログイン
- アカウントリンク: 複数のプロバイダーを 1 つのユーザー アカウントに結合します
- Cookie 内の JWT は localStorage より安全です (XSS 対策)
次の記事で詳しく説明します インターセプター、ミドルウェア、リクエストのライフサイクル。