AI-промпты

Готовые промпты, с которыми Claude Code, Cursor, Codex или Copilot подключат вход через Paspo целиком.

Один промпт — полная интеграция: кнопка входа, callback-страница, серверный маршрут, который обменивает код и проверяет id_token, и переменные окружения. В промптах есть все эндпоинты и правила Paspo, поэтому агенту не нужно ничего угадывать.

Как пользоваться

Выберите вариант

С SDK — агент установит @pingocean/paspo-sdk. Быстрее всего, попап и FedCM из коробки. Вручную — без пакета Paspo: агент использует вашу auth-библиотеку (Auth.js, Passport, Socialite, allauth, Spring Security…) или сам напишет поток OAuth 2.0 + PKCE.

Скопируйте промпт

Кнопкой копирования в правом верхнем углу блока.

Вставьте в агента

Claude Code, Cursor, Codex, Windsurf, GitHub Copilot, Devin — подойдёт любой агент, который может редактировать ваш репозиторий.

Заполните блок My app

Ваш client_id, redirect URI и scopes. Оставьте поле detect, и агент определит значение по коду.

Сначала получите клиента

Агент не может зарегистрировать OAuth-клиента за вас. Получите client_id и client_secret через Поддержку и зарегистрируйте redirect URI до запуска промпта.

Промпты на английском: так агенты точнее следуют инструкциям. Отвечать вам агент может на любом языке.

Вариант 1: с SDK

paspo-sdk-prompt.md
# Add "Sign in with Paspo" to this app using @pingocean/paspo-sdk

Paspo (https://paspo.id) is an OpenID Connect identity provider. Integrate "Sign in with Paspo" into this codebase end to end. Inspect the project first and follow its existing conventions (router, HTTP client, session handling, UI kit, TypeScript or not). Don't ask me anything you can find in the code.

## My app
- Frontend: <detect | React | Next.js | Vue | Nuxt | Angular | Svelte/SvelteKit | plain HTML>
- Backend: <detect | Node/Express | Next.js route handlers | Nuxt server routes | NestJS | Python | PHP | Go | other>
- Paspo client_id: <YOUR_CLIENT_ID>
- Redirect URI registered in Paspo: <https://app.example.com/auth/paspo/callback>
- Scopes: <openid email>   (allowed: openid, profile, email, phone)
- After sign-in: <create a session for the user | link to an existing account | issue our own JWT>

## Paspo facts (use exactly these, don't invent others)
- Issuer: https://paspo.id — discovery: https://paspo.id/.well-known/openid-configuration
- Token endpoint: POST https://paspo.id/api/w/v1/oauth/token, Content-Type application/x-www-form-urlencoded
- JWKS: https://paspo.id/api/w/v1/oauth/jwks — id_token is a JWT signed with RS256
- The authorization code is single-use and expires in 60 seconds. PKCE (S256) is mandatory.
- id_token claims: iss, sub (stable Paspo user id), aud (= client_id), exp, iat, nonce, and only for requested scopes: email, email_verified, phone_number, phone_number_verified, name.
- client_secret is server-only. It must never reach the browser bundle or any public env variable.

## Frontend
1. Install the SDK: `npm install @pingocean/paspo-sdk` (use the project's package manager). With no bundler, use `<script src="https://paspo.id/sdk/paspo.min.js"></script>`, which exposes `window.Paspo` with the same functions.
2. Add a "Sign in with Paspo" button. In its click handler call `signIn` synchronously — no `await` before it, otherwise the browser blocks the popup:
   ```js
   import { signIn, takeVerifier, takeNonce } from "@pingocean/paspo-sdk";
   signIn({
     clientId, redirectUri, scope,
     onSuccess: ({ code, state, via }) =>
       postToBackend({ code, code_verifier: takeVerifier(state), nonce: takeNonce(state), via }),
     onError: ({ error, error_description }) => showError(error, error_description),
     onClose: () => resetLoading(),
   });
   ```
   `takeVerifier` / `takeNonce` return the value once, then "". Call them once per result.
3. `signIn` runs only in the browser. Guard SSR (Next.js: a "use client" component; Nuxt: client-only or onMounted; SvelteKit: browser check). Importing the package on the server is safe.
4. Create the page at the redirect URI. It's used when the browser blocks the popup and the SDK redirects the whole tab. Read `code`, `state`, `error`, `error_description` from the query string. If `code` is present, POST `{ code, code_verifier: takeVerifier(state), nonce: takeNonce(state) }` to the backend exactly like onSuccess, then remove the query string and navigate on. If `error` is present, show it (`access_denied` = user cancelled). The redirect URI must be on the same origin as the page with the button.
5. Disable the button while a request is in flight; re-enable it on onError / onClose.

## Backend
Create `POST /auth/paspo` (adapt the path to the project's routing):
1. Validate the body: `code` (string, required), `code_verifier` (string, required), `nonce` (string, optional), `via` (optional, "fedcm").
2. Exchange the code: POST to the token endpoint, form-urlencoded:
   grant_type=authorization_code, code, client_id, client_secret (env PASPO_CLIENT_SECRET), code_verifier, redirect_uri (env PASPO_REDIRECT_URI).
   When `via === "fedcm"`, omit redirect_uri.
3. Non-200 → respond 401 with the OAuth `error` field. Never retry with the same code.
4. Verify id_token with a maintained library (Node: jose; Python: PyJWT[crypto]; PHP: firebase/php-jwt; Go: coreos/go-oidc; Java: nimbus-jose-jwt; .NET: Microsoft.IdentityModel): signature via JWKS (cache the key set), alg RS256, iss === "https://paspo.id", aud === client_id, not expired. If a nonce was sent, id_token.nonce must equal it.
5. Find or create the local user by `sub` (store it; don't use email as the primary key — the user can share a different email next time). Update email / phone from the claims.
6. Create our own session (httpOnly cookie or the project's existing mechanism). Don't send Paspo's access_token or id_token to the browser.
7. Never log code, tokens, code_verifier or client_secret.

## Deliverables
- Env variables: PASPO_CLIENT_ID (may be public), PASPO_CLIENT_SECRET (server only), PASPO_REDIRECT_URI. Add them to .env.example.
- Button component, callback page, backend route, and a short README section.
- Error handling: access_denied (cancelled), invalid_grant (code expired or already used → let the user retry), popup closed, network errors.
- Run the project's type-check / lint / tests.
- At the end, list what I must register in Paspo: the exact redirect URI and the site origin.

Вариант 2: вручную, без SDK

paspo-manual-prompt.md
# Add "Sign in with Paspo" to this app (OpenID Connect, no Paspo SDK)

Paspo (https://paspo.id) is a standard OpenID Connect provider. Integrate "Sign in with Paspo" into this codebase without any Paspo package, using the Authorization Code flow with PKCE and a server-side code exchange. Inspect the project first and follow its conventions. Don't ask me anything you can find in the code.

## My app
- Frontend: <detect | React | Next.js | Vue | Nuxt | Angular | Svelte/SvelteKit | server-rendered templates>
- Backend: <detect | Node/Express | Next.js | Nuxt | NestJS | Django | FastAPI | Laravel | Rails | Spring | ASP.NET | Go | other>
- Paspo client_id: <YOUR_CLIENT_ID>
- Redirect URI registered in Paspo: <https://app.example.com/auth/paspo/callback>
- Scopes: <openid email>   (allowed: openid, profile, email, phone; include openid)
- After sign-in: <create a session for the user | link to an existing account | issue our own JWT>

## Paspo facts (use exactly these, don't invent others)
- Issuer: https://paspo.id — discovery: https://paspo.id/.well-known/openid-configuration (prefer reading endpoints from it)
- Authorization endpoint: https://paspo.id/oauth/authorize
- Token endpoint: POST https://paspo.id/api/w/v1/oauth/token (form-urlencoded; client auth client_secret_post or client_secret_basic)
- JWKS: https://paspo.id/api/w/v1/oauth/jwks — id_token signed with RS256
- UserInfo: GET https://paspo.id/api/w/v1/oauth/userinfo with Authorization: Bearer <access_token>
- response_type=code only. PKCE S256 is mandatory. The code is single-use and expires in 60 seconds.
- id_token claims: iss, sub (stable Paspo user id), aud (= client_id), exp, iat, nonce, and only for requested scopes: email, email_verified, phone_number, phone_number_verified, name.
- No implicit flow, no iframes, no wildcard redirect URIs. client_secret is server-only.

## Approach
A. If the project already uses an auth library with generic OIDC support, register Paspo there — this is preferred:
   Auth.js / NextAuth (provider type "oidc", issuer "https://paspo.id", checks ["pkce","state","nonce"]), Passport + openid-client, Laravel Socialite (custom OIDC driver), django-allauth (openid_connect provider), Authlib, Spring Security OAuth2 Client, ASP.NET Core AddOpenIdConnect, OmniAuth openid_connect, coreos/go-oidc + golang.org/x/oauth2.
   Configure: issuer discovery, client_id, client_secret, scope, PKCE S256, state and nonce checks, the registered redirect URI.
B. Otherwise implement the flow directly on the backend:
   1. `GET /auth/paspo/login`: generate state (32 random bytes, base64url), nonce (same), code_verifier (64 random chars from [A-Za-z0-9-._~]); code_challenge = BASE64URL(SHA256(code_verifier)) without padding. Store state, nonce, code_verifier and the return URL in the server session or an encrypted httpOnly, Secure, SameSite=Lax cookie that lives 10 minutes. Respond 302 to:
      https://paspo.id/oauth/authorize?client_id=…&redirect_uri=…&response_type=code&scope=…&state=…&nonce=…&code_challenge=…&code_challenge_method=S256
   2. `GET <redirect URI>`:
      - If `error` is present, show it (access_denied = user cancelled) and clear the stored values.
      - Compare `state` with the stored value in constant time; on mismatch respond 400. Delete the stored values right away (one attempt only).
      - POST to the token endpoint: grant_type=authorization_code, code, client_id, client_secret, redirect_uri (identical to step 1), code_verifier.
      - Verify id_token with a maintained JWT/JOSE library: signature via JWKS (cache keys, refetch on unknown kid), alg RS256, iss === "https://paspo.id", aud === client_id, exp, and nonce === stored nonce.
      - Find or create the local user by `sub`; update email / phone from claims. Create our own session and redirect to the return URL (only allow same-site paths).
   3. Frontend: a "Sign in with Paspo" link or button that navigates to `/auth/paspo/login` (full navigation, not fetch).

## Rules
- client_secret only from a server env variable (PASPO_CLIENT_SECRET). Never in the frontend bundle.
- Don't send Paspo tokens to the browser and don't log code, tokens, code_verifier or client_secret.
- Don't use email as the user primary key — use sub.

## Deliverables
- Env variables: PASPO_CLIENT_ID, PASPO_CLIENT_SECRET, PASPO_REDIRECT_URI, PASPO_ISSUER=https://paspo.id; add to .env.example.
- Login route, callback route, sign-in button, logout of our own session, and a short README section.
- Error handling: access_denied, invalid_grant (expired / reused code → restart login), state mismatch, network errors.
- Run the project's type-check / lint / tests.
- At the end, list what I must register in Paspo: the exact redirect URI.

Что сделает агент

С SDKВручную
Интерфейс входаПопап поверх страницы, FedCM в Chromium 141+Полностраничный редирект на Paspo
PKCE, state, nonceГенерирует SDK в браузереГенерируются и хранятся на вашем сервере
Callback-страницаФолбэк при заблокированном попапеОсновной поток
Обмен кодаВаш бэкенд, POST /auth/paspoВаш бэкенд, callback-маршрут
Новые зависимости@pingocean/paspo-sdk + JWT-библиотекаТолько JWT / OIDC-библиотека

Советы

  • Запускайте промпт из корня репозитория, чтобы агент видел и фронтенд, и бэкенд.
  • В монорепозитории укажите пакеты в полях Frontend / Backend, например apps/web (Next.js), apps/api (NestJS).
  • Когда агент закончит, проверьте: успешный вход, закрытие окна и вход с заблокированным попапом.
  • Нужна мобильная интеграция? См. Android.