🔐
OAuth、OIDCについてまとめてみた
OAuth
- OAuthとは → ユーザー名やパスワードを渡さずに、あるサービスが別のサービスにアクセスを許可する仕組み。例えば、Googleアカウントの許可を得て、他のアプリがユーザーの情報にアクセスできるようにする仕組み
- OAuthは認可の仕組み → 認証の仕組み(ログイン)はアプリ側で実装する必要がある
- OAuthの登場人物
- リソースオーナー: アクセスを許可するユーザー
- クライアント: ユーザーのデータを取得したいアプリ
- 認可サーバー: ユーザーの認可を管理し、アクセストークンを発行
- リソースサーバー: ユーザーのデータを管理し、アクセストークンを使ってアクセスを制御
- 説明では、以下の役割で整理する
- ブラウザ(= リソースオーナー)
- Webアプリ(= クライアント)
- Webサーバー
- データベース
- セッションストア(例: Redis)
- 認可・リソースサーバー(例: GitHub)
OAuth認可フロー
- ユーザーが認可・リソースサーバーの認証画面に遷移し、認証後、アクセストークンが生成・発行されるまでの流れ
- 事前に必要な設定
- 認可・リソースサーバーの管理画面からclient_id, client_secretを取得し、Webアプリに設定する
- 認可・リソースサーバーにWeb アプリのcallback_uri(リダイレクト先)を許可されたリダイレクトURIとして登録する
コード例
- 認可・リソースサーバー
- GET /oauth/authorize
- POST /oauth/authorize
- POST /oauth/access_token
import express from 'express';
import jwt from 'jsonwebtoken';
...
const app = express();
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
const redirectUri = process.env.REDIRECT_URI;
app.get("/oauth/authorize", (req, res) => {
const { client_id } = req.query;
const client = await getClientInfo(client_id);
if (!client) {
return res.status(400).json({ error: 'Invalid client_id' });
}
// 認証画面
res.send('<form action="/oauth/authorize" method="POST">
<input name="email" type="email" placeholder="メールアドレス" required />
<button type="submit">ログイン</button>
</form>');
});
app.post("/oauth/authorize", (req, res) => {
const { email, password } = req.body;
// データベースからユーザー情報を取得
const user = await db.getUserByEmail(email);
if (!user) {
res.status(401).json({ message: 'Unauthorized' });
}
// パスワード照合
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
res.status(401).json({ message: 'Unauthorized' });
}
// 認可コードをUUIDとして発行し、DBに保存
const authCode = uuidv4();
await storeAuthCode(authCode, email);
res.redirect(`${redirectUri}?code=${authCode}`);
});
app.post("/oauth/access_token", (req, res) => {
const { code } = req.body;
try {
const email = await getEmailFromAuthCode(code);
if (!email) {
return res.status(400).json({ error: "無効または期限切れの認可コード" });
}
await deleteAuthCode(code);
// アクセストークンを生成、発行
const accessToken = jwt.sign({ email }, process.env.CLIENT_SECRET, { expiresIn: "1h" });
res.json({ access_token: accessToken });
} catch (error) {
console.error("エラー:", error);
res.status(500).json({ error: "サーバーエラー" });
}
});
- Webサーバー
- GET /oauth/callback
import express from 'express';
import jwt from 'express-session';
app.get("/oauth/callback", async (req, res) => {
const { code } = req.query;
if (!code) {
return res.status(400).json({ error: "認可コードが必要です" });
}
try {
// 認可コードに紐づいたクライアント情報をDBから取得
const clientInfo = await getClientInfoFromCode(code);
if (!clientInfo) {
return res.status(400).json({ error: "無効または期限切れの認可コード" });
}
const { client_id, client_secret, redirect_uri } = clientInfo;
// 認可コードを使ってアクセストークンを取得
const tokenResponse = await axios.post("https://auth.example.com/oauth/access_token", {
code,
client_id,
client_secret,
grant_type: "authorization_code",
redirect_uri,
});
} catch (error) {
console.error("OAuth エラー:", error);
res.status(500).send("認証エラー");
}
});
OAuthログインフロー
- 発行されたアクセストークンを使ってユーザー情報を取得し、ブラウザにセッションIDが設定されるまで
OAuthを使わない場合
ユーザーがemailとpasswordを送信し、ブラウザにセッションIDが設定されるまでの流れ
OIDC(OpenID Connect)
- OIDCとは → OAuth2.0に「認証機能」を追加した仕組み
- OAuth2.0では、ユーザー情報を取得するために、アクセストークンを使って認可サーバーに問い合わせる必要があった
- OIDCでは「IDトークン」が発行され、ユーザー情報が含まれる
- Webアプリは IDトークンを確認するだけで、ユーザーを識別できる
- 認可サーバーに毎回アクセスする必要がない
- JWK(公開鍵)はキャッシュできるため、署名検証のたびに取得する必要がない
- 毎回認可サーバーに問い合わせなくてもログインできる
- OIDCの登場人物
- エンドユーザー: アクセスを許可するユーザー
- Relying Party: ユーザーのデータを取得したいアプリ
- IdP: ユーザーの認証を管理し、IDトークンを発行、OAuthだと認可・リソースサーバー
- 説明では、以下の役割で整理する
- ブラウザ(= エンドユーザー)
- Webアプリ(= Relying Party)
- Webサーバー
- データベース
- セッションストア(例: Redis)
- IdP(例: GitHub)
OIDCログインフロー
OAuthとOIDCのログインフローを比較
参考資料
Discussion