๐ ์น ํตํฉ ํธ๋ โ
Source:
tracks/web.mdยท Live: https://docs.1pass.dev/tracks/web LLM-sanitized: internal links absolutized, VitePress containers โ admonitions, line numbers in the Jump-to Index reference this rendered file (1-indexed).
๐ Jump-to Index โ
- L30-L43: ## ํต์ฌ ์ฝ์ 4์ค
- L44-L54: ## Step 1 ยท ์ฑ ๋ฑ๋ก
- L55-L91: ## Step 2 ยท ํตํฉ ํจํด
- L57-L81: ### SPA / ํด๋ผ์ด์ธํธ ์ฌ์ด๋ (๊ถ์ฅ)
- L82-L91: ### ์๋ฒ ์ฌ์ด๋ (Next.js / Rails / Express)
- L92-L121: ## Step 3 ยท ์น ํนํ ํจ์ ํผํ๊ธฐ
- L122-L131: ## Step 4 ยท ๋น๋ ์ ์ฒดํฌ
- L132-L141: ## ๊ณตํต ๋ ํผ๋ฐ์ค (ํธ๋ ๋ฌด๊ด)
- L142-L149: ## AI ์๊ฒ ํต์งธ๋ก ๋์ง๊ธฐ
๐ ์น ํตํฉ ํธ๋ โ
Next.js ยท Rails ยท Express ๋ฑ ์๋ฒ ์ฌ์ด๋ ์น ์ฑ์์ logi ๋ฅผ ํตํฉํ๋ ๊ฐ์ฅ ๋น ๋ฅธ ๊ธธ.
๐ก Tip: ์ด ํธ๋์ด ๋ง๋์?
- โ ๋ง์: ๋ฐฑ์๋๊ฐ ์๊ณ , ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์ ์ํ๋ ์น ์๋น์ค
- โ ๋ชจ๋ฐ์ผ ์ฑ ์ด๋ผ๋ฉด โ ๐ฑ ๋ชจ๋ฐ์ผ ์ฑ ํตํฉ ํธ๋
- โ ์์ SPA (๋ฐฑ์๋ ์์) ์ด๋ผ๋ฉด โ Public Client + PKCE. public-clients ์ฐธ๊ณ
- โ ๋ฐ์คํฌํฑ SSO (Apple / Google ID ํ ํฐ ์ง์์ ) ์ด๋ผ๋ฉด โ Web SSO (๋ฐ์คํฌํฑ)
ํต์ฌ ์ฝ์ 4์ค โ
- Confidential client +
client_secret์๋ฒ ๋ณด๊ด. ์ ๋ ๋ธ๋ผ์ฐ์ ๋ฒ๋ค์ ๋ ธ์ถ ๊ธ์ง. - redirect_uri ๋
https://...๋๋http://localhostโ ์๋ฒ ์ฝ๋ฐฑ ๋ผ์ฐํธ ๊ฒฝ๋ก. - state + nonce ๊ฒ์ฆ + HttpOnly ยท Secure ์ฟ ํค ๋ก ์ธ์ ๋ฐ๊ธ.
- ์ฑ RP ์ ์น RP ๋ ๋ถ๋ฆฌ โ ์์ธํ: ๊ณตํต/Public Clients ยท Surface ๋ณ RP ๋ถ๋ฆฌ
โ ๏ธ Warning: client_id ๋ฅผ ๋ถ๋ฆฌํ๋ ๊ณต์ ํ๋ โ ์ฌ์ฉ์ ์๋ณ ํค๋
canonical_subsurface ๋ณ๋ก client_id ๋ฅผ ๋ถ๋ฆฌํ๋ฉด ๊ฐ์ ์ฌ์ฉ์๊ฐ client ๋ง๋ค ๋ค๋ฅธ pairwisesub์ ๋ฐ๊ณ , ํ๋์ client_id ๋ฅผ ๊ณต์ ํ๋ฉด ๊ฐ์sub์ด ๋์ต๋๋ค. ์ด๋ ์ชฝ์ ํํ๋ , RP ๊ฐsub์ผ๋ก ์ฌ์ฉ์๋ฅผ ํค์ํ๋ฉด ๋ถ๋ฆฌ ๊ตฌ์ฑ์์ ๋ ๋ฒ์งธ surface ๋ก๊ทธ์ธ ์AlreadyLinkedError๋ก ๊นจ์ง ์ ์์ต๋๋ค.
โ ์ฌ๋ ๋จ์ ์๋ณ์ ํญ์ client ์ ์ฒด์์ ๋์ผํ canonical_sub(userinfo ๊ฐ ํญ์ emit) ๋ก ํ์ธ์. ์์ธ: Sub ์ ์ฑ
Step 1 ยท ์ฑ ๋ฑ๋ก โ
โ ๏ธ Warning: ์ฑ(RP) ๋ฑ๋ก ์ ์ด๋ฉ์ผ ์ธ์ฆ ํ์ ๊ฐ๋ฐ์ ์ฝ์์์ ์ฑ(RP)์ ๋ฑ๋กํ๋ ค๋ฉด ๋จผ์ ์ด๋ฉ์ผ ์ธ์ฆ์ด ํ์ํฉ๋๋ค. ๊ฐ์ ํ ์ธ์ฆ ๋ฉ์ผ์ ๋งํฌ๋ฅผ ํด๋ฆญํ๊ฑฐ๋ ์ฝ์
/account์์ ์ฌ๋ฐ์กํ์ธ์. ๋ฏธ์ธ์ฆ ์ ๋ฑ๋ก์ด ์ฐจ๋จ๋ฉ๋๋ค. โ ์ฑ ๋ฑ๋ก ๊ฐ์ด๋ ยท ์ฌ์ ์๊ฑด
SPA / ํด๋ผ์ด์ธํธ ์ฌ์ด๋ โ
client_type: public์ผ๋ก RP ๋ฑ๋ก (PKCE-only, secret ์์).์๋ฒ ์ฌ์ด๋ (Next.js / Rails / Express) โ
client_type: confidential๋ก RP ๋ฑ๋ก โclient_id+client_secret๋ฐ๊ธ.
Step 2 ยท ํตํฉ ํจํด โ
SPA / ํด๋ผ์ด์ธํธ ์ฌ์ด๋ (๊ถ์ฅ) โ
์์ SPA (๋ฐฑ์๋ ์ฝ๋ฐฑ ์์ด ๋ธ๋ผ์ฐ์ ์์ ์ง์ ํ ํฐ ๊ตํ) ๋ผ๋ฉด ๊ณต์ SDK @logi-auth/browser (v1.0.1 ์ด์) ์ฌ์ฉ:
npm install @logi-auth/browserimport { LogiAuth } from '@logi-auth/browser';
const auth = new LogiAuth({
clientId: 'logi_xxx',
redirectUri: window.location.origin + '/auth/callback',
});
// Page A โ ๋ก๊ทธ์ธ ์์
await auth.signIn();
// Page B โ ์ฝ๋ฐฑ ํ์ด์ง
const tokens = await auth.handleCallback();์์ธํ API + ์๋ฌ ๋ถ๋ฅ โ SPA Quickstart
์๋ฒ ์ฌ์ด๋ (Next.js / Rails / Express) โ
์๋ฒ์์ client_secret ๋ณด๊ด + ์ฝ๋ฐฑ ์ฒ๋ฆฌ. logi ๋ ํ์ค OIDC discovery (https://api.1pass.dev/.well-known/openid-configuration) ๋ฅผ ์ ๊ณตํ๋ฏ๋ก ์ผ๋ฐ OIDC ๋ผ์ด๋ธ๋ฌ๋ฆฌ โ oauth4webapi, openid-client, next-auth, auth.js โ ๊ฐ issuer: 'https://api.1pass.dev' ๋ง์ผ๋ก ์๋ ์ค์ ๋ฉ๋๋ค.
| ์คํ | ๊ฐ์ด๋ | ํต์ฌ |
|---|---|---|
| Next.js (App Router) | integrations/nextjs | Route Handler + iron-session ํจํด |
| Rails 8 | integrations/rails | Direct OAuth ํด๋ผ์ด์ธํธ (omniauth ๋ฏธ์ฌ์ฉ). โ ๏ธ Hotwire/Turbo ์ฌ์ฉ ์ data-turbo="false" ํ์ |
| Express.js | integrations/express | cookieParser + crypto PKCE |
Step 3 ยท ์น ํนํ ํจ์ ํผํ๊ธฐ โ
โ ๏ธ Warning: โ ๏ธ ๊ธฐ์กด ๋ชจ๋ฐ์ผ RP ์ ์น surface ์ถ๊ฐ ์ redirect_uri ํ์ดํธ๋ฆฌ์คํธ ๊ฐฑ์ ํ์ ํ๋์
client_id๋ฅผ ๋ชจ๋ฐ์ผ ์ฑ๊ณผ ์น surface ๊ฐ ๊ณต์ ํ๋ฉด (public + PKCE RP ๋ผ๋ฉด ์์ ), ์น ์ฝ๋ฐฑ URL ๋ RP ์redirect_urisํ์ดํธ๋ฆฌ์คํธ์ ๋ช ์์ ์ผ๋ก ๋ฑ๋ก๋์ด ์์ด์ผ ํฉ๋๋ค. ๋๋ฝ ์ logi ๋ ์ฆ์ ๊ฑฐ์ :
{ "error": "invalid_request", "error_description": "redirect_uri not registered" }์น ๋น๋ ์์ ์ ๋ฐ๋์:
# ํ์ฌ ๋ฑ๋ก๋ ํ์ดํธ๋ฆฌ์คํธ ํ์ธ
logi app show $CLIENT_ID
# ๋๋ฝ์ด๋ฉด ์ถ๊ฐ (๊ธฐ์กด URI ์ ์งํ๊ณ append ํจ)
logi app update $CLIENT_ID --add-redirect-uri "https://app.example.dev/auth/1pass/callback"
# preview/staging ๋๋ฉ์ธ์ด ์๋ค๋ฉด ๊ฐ์ด ๋ฑ๋ก
logi app update $CLIENT_ID --add-redirect-uri "https://preview.example.dev/auth/1pass/callback"
# ๊ฒ์ฆ
logi apps verify $CLIENT_ID -r "https://app.example.dev/auth/1pass/callback"์ด ํจ์ ์ โ์ด๋ฏธ ๋ชจ๋ฐ์ผ๋ก ๋ฑ๋ก๋ RP ์ ์น ํ๋ฆ์ ์๋ก ๋ถ์ผ ๋โ ์์ฃผ ๋ฐ์ํฉ๋๋ค. ์ ๊ท RP ๋ผ๋ฉด ์ฑ ๋ฑ๋ก ๊ฐ์ด๋ ์ redirect_uris ๋ฐฐ์ด์ ๋ชจ๋ surface ์ ์ฝ๋ฐฑ์ ์ฒ์๋ถํฐ ํ ๋ฒ์ ๋ฑ๋กํ์ธ์.
- ์๋ต ํค๋ / Body ์๊ทธ๋ โ
Set-CookieSameSite ์ ์ฑ - Web SSO (๋ฐ์คํฌํฑ Apple/Google) โ ๋ด๋ถ ๋์ ์ฐธ๊ณ ์ฉ. ์ ๊ท RP ํตํฉ์ ์์์ ์ ์๋๋ฉฐ, logi ๊ฐ Apple/Google upstream IdP ํ ํฐ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ์๊ณ ์ถ์ ๋๋ง ์ฐธ๊ณ ํ์ธ์
- QR ๋ก๊ทธ์ธ (์ฑ ํธ์ ์น์ธ) โ ๋ฐ์คํฌํฑ ์น์์ ๋ชจ๋ฐ์ผ ์ฑ์ผ๋ก ์น์ธ ์์
- Widget SDK (iframe embed) โ ๋ก๊ทธ์ธ ํ๋ฉด์ iframe ์ผ๋ก ๋ผ์๋ฃ๋ ํจํด
- ์ฒซ ๋ก๊ทธ์ธ ์๋ฃ ํผ โ ์์ฒด ํ์๊ฐ์ ์ถ๊ฐ ํ๋ ํจํด
Step 4 ยท ๋น๋ ์ ์ฒดํฌ โ
- โ
redirect_uris ํ์ดํธ๋ฆฌ์คํธ ๊ฒ์ฆ โ ๋น๋/๋ฐฐํฌ ์ง์
logi apps verify $CLIENT_ID -r "$WEB_CALLBACK_URL"๋ก production / staging / preview ๋๋ฉ์ธ ๊ฐ๊ฐ ํ์ธ. CI ์ ๋ฐ์๋๋ฉด ์์ (--json+ exit code) - RP ํตํฉ ํ ์คํธ
- ๋ณด์ Best Practices (RP ์ธก) โ state/nonce/CSRF
- Troubleshooting
demo.1pass.dev/qrโ ๋ฐ์คํฌํฑ โ ๋ชจ๋ฐ์ผ QR ํธ๋์คํ walking sample (์๋๋ฆฌ์ค deep-link)demo.1pass.dev/oauthโ ๋ฐ์คํฌํฑ OAuth (PKCE) walking sample- Demo ํ์ด์ง ๋๋ฌ๋ณด๊ธฐ โ ์๋๋ฆฌ์ค๋ณ ํ๋ฆ + CSP ์ฒญ์ ํจํด
๊ณตํต ๋ ํผ๋ฐ์ค (ํธ๋ ๋ฌด๊ด) โ
- Authorization Code Flow ยท PKCE ยท Scope ๋ ํผ๋ฐ์ค
- ์ค๋ฅ ์ฝ๋ ยท Token Introspection & JWKS
- Refresh Token ์ ์ฑ
- ๋ก๊ทธ์ธ ๋ฒํผ ์ปดํฌ๋ํธ
- Webhook ์ฐ๋ ยท HMAC ์๋ช ๊ฒ์ฆ
AI ์๊ฒ ํต์งธ๋ก ๋์ง๊ธฐ โ
@/llms-full.txt ๋ฅผ Claude Code ยท Cursor ยท Codex ์ ๋ถ์ด๊ณ ๋ค์ ํ ์ค:
"logi 1pass ๋ฅผ [Next.js / Rails / Express] ์น ์ฑ์ RP ๋ก ํตํฉํด์ค. confidential client + ์๋ฒ ๋ณด๊ด secret ๊ธฐ์ค."
โ env, route, controller, callback handler, ๋ก๊ทธ์ธ ๋ฒํผ UI ๊น์ง ์๋ ์์ฑ๋ฉ๋๋ค.