Integrasi Node.js Backend
Recipe ini menunjukkan cara backend Node.js melakukan sign in pengguna dengan Muhajir Studio, menyimpan OAuth state di sisi server, dan memanggil external API dari kode server yang tepercaya.
Kapan Menggunakan Pola Ini
Gunakan pola ini ketika aplikasi Anda memiliki backend yang dapat mempertahankan HTTP-only application session. Browser hanya menerima session cookie milik aplikasi Anda; OIDC tokens tetap berada di server Anda.
| Service | URL Produksi | Digunakan untuk |
|---|---|---|
| API | https://api.muhajirstudio.com/ | Authorization, token exchange, refresh, dan /api/external/*. |
| Main web | https://login.muhajirstudio.com/ | Login pengguna, signup, email verification, dan consent. |
| Admin web | https://admin.muhajirstudio.com/ | Application registration. |
Setup Admin
Minta admin mendaftarkan aplikasi Anda di web-admin dengan:
| Field | Contoh | Catatan |
|---|---|---|
| Allowed web origin | https://app.example.com | Origin saja; tanpa path, query, fragment, atau wildcard. |
| Redirect path | /auth/callback | Path saja. |
| Redirect URI | https://app.example.com/auth/callback | Diturunkan dari origin + path. |
| Scopes | openid profile email offline_access | Scope yang dipilih admin adalah source of truth. |
Applications yang dibuat admin adalah Public PKCE clients. Aplikasi menerima client_id dan tidak menerima client_secret.
1. Mulai Login
Buat route login di aplikasi Anda yang menghasilkan nilai PKCE dan CSRF, menyimpannya di server session, lalu mengarahkan browser ke Muhajir Studio.
Pada contoh di bawah, apiBaseUrl adalah production API URL dan clientId adalah public client ID dari web-admin.
app.get('/login', async (req, res) => {
const codeVerifier = randomUrlSafeString();
const codeChallenge = await sha256Base64Url(codeVerifier);
const state = randomUrlSafeString();
const nonce = randomUrlSafeString();
req.session.oidc = { codeVerifier, state, nonce };
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: 'https://app.example.com/auth/callback',
response_type: 'code',
scope: 'openid profile email offline_access',
state,
nonce,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
res.redirect(`${apiBaseUrl}/api/auth/oauth2/authorize?${params}`);
});
code_challenge_method harus S256. Plain PKCE challenges tidak diterima.
2. Tangani Callback
Setelah pengguna sign in, melakukan email verification jika diperlukan, dan memberi consent, Muhajir Studio mengarahkan kembali ke callback terdaftar dengan code dan state.
Callback Anda harus memverifikasi state yang kembali sebelum token exchange.
app.get('/auth/callback', async (req, res) => {
if (req.query.state !== req.session.oidc?.state) {
res.status(400).send('Invalid state');
return;
}
const tokenRes = await fetch(`${apiBaseUrl}/api/auth/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: String(req.query.code),
code_verifier: req.session.oidc.codeVerifier,
client_id: clientId,
redirect_uri: 'https://app.example.com/auth/callback',
}),
});
const tokenBody = await tokenRes.json();
if (!tokenRes.ok) {
res.status(400).json(tokenBody);
return;
}
// Simpan tokens di sisi server, terkait dengan application session Anda.
req.session.tokens = tokenBody;
delete req.session.oidc;
res.redirect('/');
});
Jangan kirim client_secret untuk Public PKCE clients yang dibuat admin.
3. Panggil External API
Gunakan access token dari server Anda untuk memanggil stable external endpoints.
const meRes = await fetch(`${apiBaseUrl}/api/external/me`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (meRes.status === 401) {
// Token hilang, expired, unknown, atau tidak lagi valid.
}
if (meRes.status === 403) {
// Token valid tetapi tidak memiliki required scope seperti openid.
}
GET /api/external/me selalu membutuhkan openid. Field tambahan hanya dikembalikan ketika token memiliki scope yang sesuai seperti profile, email, atau permissions.
4. Refresh Tokens
Jika token response menyertakan refresh_token, simpan secara terenkripsi atau di protected server-side session store. Gunakan hanya dari backend Anda.
POST /api/auth/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=CLIENT_ID
Jika refresh gagal dengan invalid_grant, hapus tokens yang tersimpan dan arahkan pengguna melalui login lagi.
Checklist Produksi
- Simpan
state,nonce, dancode_verifierdi sisi server per login attempt. - Validasi
statesebelum menukarcode. - Jangan pernah mengirim
client_secretuntuk Public PKCE clients. - Simpan tokens hanya di server, bukan di storage yang dapat diakses browser.
- Daftarkan production origin dan callback path yang tepat.
- Tangani OAuth callback errors seperti
error=access_denied. - Perlakukan
401sebagai perlu re-authentication dan403sebagai missing scope atau insufficient consent.