Web アプリケーション開発時のセキュリティ対策
🔐 1. 認証 & セッション管理
パスワード管理
const { data, error } = await supabase.auth .signUp ({
email : 'user@example.com' ,
password : 'secure-password-123'
});
コピー
セッション管理
import { setCookie } from 'hono/cookie' ;
setCookie (c, 'auth_token' , tokenValue, {
httpOnly : true ,
secure : true ,
sameSite : 'Strict' ,
maxAge : 86400 ,
domain : 'example.com' ,
path : '/' ,
});
コピー
MFA (多要素認証)
const { data, error } = await supabase.auth .mfa .enroll ({
factorType : 'totp'
});
コピー
🔒 2. 入力バリデーション & サニタイゼーション
フロントエンド
import { z } from 'zod' ;
const loginSchema = z.object ({
email : z.string ().email (),
password : z.string ().min (8 ),
});
const result = loginSchema.safeParse (formData);
if (!result.success ) {
console .log (result.error );
}
コピー
バックエンド
const query = `SELECT * FROM users WHERE id = ${userId} ` ;
const { data } = await supabase
.from ('users' )
.select ('*' )
.eq ('id' , userId);
const userIdSchema = z.string ().uuid ();
const validId = userIdSchema.parse (userId);
コピー
HTML/JavaScript インジェクション
<div dangerouslySetInnerHTML={{ __html : userInput }} />;
<div > {userInput}</div > ;
コピー
🛡️ 3. CSRF (Cross-Site Request Forgery) 対策
原理
攻撃:
1. 攻撃者が malicious 例: http://evil.com
2. ユーザーが Bank サイト login 中
3. ユーザーが evil.com 訪問
4. evil.com が自動送信: POST /bank/transfer?amount=10000
5. ユーザー Cookie で認証されるため実行される
コピー
対策
setCookie (c, 'session' , token, {
sameSite : 'Strict' ,
});
const csrfToken = crypto.randomUUID ();
setCookie (c, 'csrf_token' , csrfToken);
<form action ="/api/transfer" method ="POST" >
<input type ="hidden" name ="csrf_token" value ={csrfToken} />
<input type ="text" name ="amount" />
</form >
app.post ('/api/transfer' , (c ) => {
const token = c.req .header ('x-csrf-token' );
const valid = token === c.req .query ('csrf_token' );
if (!valid) return c.json ({ error : 'CSRF' }, 403 );
});
コピー
👮 4. 認可 (Authorization)
Role-Based Access Control (RBAC)
CREATE POLICY "users can only read own data"
ON users FOR SELECT
USING (auth.uid () = id);
CREATE POLICY "only admins"
ON admin_panel FOR ALL
USING (
(SELECT is_admin FROM users WHERE id = auth.uid ()) = true
);
コピー
Attribute-Based Access Control (ABAC)
app.put ('/articles/:id' , async (c) => {
const articleId = c.req .param ('id' );
const userId = c.get ('userId' );
const { data : article } = await supabase
.from ('articles' )
.select ('user_id' )
.eq ('id' , articleId)
.single ();
if (article.user_id !== userId) {
return c.json ({ error : 'Forbidden' }, 403 );
}
});
コピー
🌐 5. CORS (Cross-Origin Resource Sharing)
脆弱な設定
app.use (cors ({ origin : '*' }));
コピー
安全な設定
app.use (cors ({
origin : [
'https://example.com' ,
'https://admin.example.com' ,
],
credentials : true ,
methods : ['GET' , 'POST' , 'PUT' , 'DELETE' ],
allowedHeaders : ['Content-Type' , 'Authorization' ],
}));
app.use (cors ({
origin : (origin ) => {
const allowed = ['example.com' , 'admin.example.com' ];
if (allowed.some (a => origin?.includes (a))) return true ;
return false ;
},
}));
コピー
🔑 6. 環境変数 & シークレット管理
❌ 危険なパターン
DATABASE_URL =postgres :
API_KEY =super -secret-key
コピー
✅ 安全なパターン
echo ".env .env.local .env.*.local" >> .gitignore
NEXT_PUBLIC_API_URL =https :
DATABASE_URL =postgres :
SERVICE_ROLE_KEY =xxx
コピー
API Key ローテーション
🚨 7. エラーハンドリング & ロギング
❌ 不適切なエラー表示
app.onError ((err, c ) => {
return c.json ({
error : err.message ,
stack : err.stack ,
}, 500 );
});
コピー
✅ 適切なエラー処理
app.onError ((err, c ) => {
console .error ('Error:' , err);
return c.json ({
error : 'Internal Server Error' ,
code : 'ERR_INTERNAL'
}, 500 );
});
try {
await supabase.from ('users' ).insert (data);
} catch (err) {
if (err.code === '23505' ) {
return c.json ({ error : 'Email already exists' }, 400 );
}
console .error ('DB Error:' , err);
return c.json ({ error : 'Server error' }, 500 );
}
コピー
セキュアロギング
console .log ('User login:' , { email, password, ip });
console .log ('User login' , { email : sanitize (email), ip });
コピー
🔐 8. HTTPS & TLS
app.use (async (c, next) => {
if (c.req .header ('x-forwarded-proto' ) !== 'https' ) {
const url = c.req .url .replace ('http://' , 'https://' );
return c.redirect (url, 301 );
}
await next ();
});
コピー
app.use (async (c, next) => {
c.header ('Strict-Transport-Security' , 'max-age=31536000; includeSubDomains' );
c.header ('X-Content-Type-Options' , 'nosniff' );
c.header ('X-Frame-Options' , 'DENY' );
c.header ('Content-Security-Policy' , "default-src 'self'" );
await next ();
});
コピー
🛡️ 9. Rate Limiting & DDoS 対策
API Rate Limiting
import { RateLimiter } from 'hono/rate-limiter' ;
const limiter = new RateLimiter ({
windowMs : 60 * 1000 ,
max : 100 ,
});
app.use (limiter);
const limits = new Map ();
app.use (async (c, next) => {
const ip = c.req .header ('cf-connecting-ip' ) || 'unknown' ;
if (limits.get (ip) > 100 ) {
return c.json ({ error : 'Too Many Requests' }, 429 );
}
limits.set (ip, (limits.get (ip) || 0 ) + 1 );
await next ();
});
コピー
DDoS 対策
📋 10. データ保護
パスワードリセット
const token = crypto.randomBytes (32 ).toString ('hex' );
const hashedToken = sha256 (token);
await supabase.from ('password_reset' ).insert ({
user_id,
token_hash : hashedToken,
expires_at : new Date (Date .now () + 3600000 ),
});
const resetUrl = `https://example.com/reset/${token} ` ;
コピー
データ暗号化
GDPR コンプライアンス
✅ デプロイ前チェックリスト
🚀 運用時
定期確認
インシデント対応
参考リソース