GDSC Chuo Membership

Authentication

OAuth 2.0 の authorization code grant と PKCE に似た流れでセッショントークンが発行されます。

Get authorization code

まずユーザーを /login にリダイレクトします。このとき code_verifiercode_challenge が必要です。code_verifier はランダムに生成された文字列で、code_challenge はそれを ASCII エンコードし、SHA-256 でハッシュ化したものです。code_verifier はデバイスまたはブラウザーに保存してください。

https://gdsc-chuo-membership-backend.vercel.app/login?code_challenge=[CODE_CHALLENGE]
import { sha1 } from "@oslojs/crypto/sha1";
import { base64url } from "@oslojs/encoding";

const codeVerifier = generateCodeVerifier();
localStorage.setItem("code_verifier", codeVerifier);
const codeChallenge = generateCodeChallenge(codeVerifier);

const url = new URL("https://gdsc-chuo-membership-backend.vercel.app/login");
url.searchParams.set("code_challenge", codeChallenge);

window.location.href = url.toString();

function generateCodeVerifier(): string {
    const randomBytes = new Uint8Array(32);
    crypto.getRandomValues(randomBytes);
    const codeVerifier = base64url.encodeNoPadding(randomBytes);
    return codeVerifier;
}

function generateCodeChallenge(codeVerifier: string): string {
    const bytes = new TextEncoder().encode(codeVerifier);
    const codeChallenge = base64url.encodeNoPadding(sha256(bytes));
    return codeChallenge;
}

Exchange authorization code for session token

/login/callback にユーザーがリダイレクトされます。

/login/callback?login_id=[LOGIN_ID]

login_id が含まれているので、それと保存した code_verifier/session に POST するとセッショントークンを得られます。

const searchParams = new URLSearchParams(window.location.search);
const loginId = searchParams.get("login_id");
const codeVerifier = localStorage.get("code_verifier");

const response = await fetch("https://gdsc-chuo-membership-backend.vercel.app/session", {
    method: "POST",
    body: JSON.stringify({
        login_id: loginId,
        code_verifier: codeVerifier,
    }),
    headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
    },
});

if (response.ok) {
    const result = await response.json();
    const sessionToken = result.session;
    localStorage.set("session", sessionToken);
}

Make authenticated requests

セッショントークンは Authorization ヘッダーで送ってください。

Authorization: Session [SESSION_TOKEN]

また、セッションの有効期限(UNIX 秒)が X-Session-Expires-At ヘッダーとしてすべてのレスポンスに含まれます。

Sign out

/session に DELETE リクエストを送ることでログアウトできます。

await fetch("https://gdsc-chuo-membership-backend.vercel.app/session", {
    method: "DELETE",
    headers: {
        Authorization: `Session ${sessionToken}`,
    },
});

CORS

frontend.com からのブラウザーリクエストのみが許可されています。