logi RP Integration
This skill helps an AI assistant integrate logi (the consumer-facing 1pass identity service) into a Relying Party (RP) project. logi is a hosted OIDC-style IdP running live at https://api.1pass.dev — you do not need to self-host anything. You register your app, drop in standard OAuth wiring, and verify.
Mental model. logi behaves like an OpenID Connect provider for the standard web/server flow (Authorization Code + PKCE, OIDC discovery), but the mobile login experience is a Kakao/Naver-style "first-try" app-to-app handoff, not a plain ASWebAuthenticationSession. On native platforms, always use the published logi SDK or the documented first-try pattern — never a bare web-auth session (see Anti-pattern).
Authoritative docs (always prefer these over guessing — fetch the latest before non-trivial work):
- Full doc bundle for LLMs:
https://docs.1pass.dev/llms-full.txt - Topic index:
https://docs.1pass.dev/llms.txt - Per-topic markdown:
https://docs.1pass.dev/llms/<path>.md(e.g.llms/integrations/rails.md) - Developer console (register apps, manage scopes, secrets):
https://start.1pass.dev/developer/applications - Interactive flow demos:
https://demo.1pass.dev/ - Self-service login diagnostics for end users:
https://api.1pass.dev/diagnose
Conventions used throughout
| Item | Convention |
|---|---|
| IdP base URL | https://api.1pass.dev (set as LOGI_API_URL, no change needed) |
| Env var prefix | LOGI_* |
| Start route (web) | /auth/logi/start |
| Callback route (web) | /auth/logi/callback |
| Scopes | openid profile:basic email (standard SSO) |
| Mobile app id (first-try target) | dev.onepass.app (Android package) / applinks: + custom scheme (iOS) |
| Diagnostics | https://api.1pass.dev/diagnose |
Replace placeholders (logi_xxx, https://app.example.com, <your-app-host>) with the developer's real values.
Phase 0 — Pre-flight
Confirm with the developer:
- The project already has working user auth (a place to create/sign in a session).
- It is deployed somewhere you can set environment variables (Render, Vercel, Fly, Heroku, a VPS, etc.), or runs locally for first integration.
- The production (or local) URL — you'll build
redirect_urifrom it, e.g.https://app.example.com/auth/logi/callback.
PROJECT=<name>
PROJECT_DIR=<path to project>
SERVICE_URL=https://app.example.com # the developer's real host (or http://localhost:PORT for dev)Phase 1 — Detect the stack
detect_stack() {
local p="$1"
local gemfile
gemfile=$(find "$p" -maxdepth 3 -name Gemfile -not -path '*/node_modules/*' 2>/dev/null | head -1)
if [ -n "$gemfile" ]; then
grep -q inertia_rails "$gemfile" && echo rails-inertia && return
grep -qE 'turbo-rails|stimulus' "$gemfile" && echo rails-hotwire && return
echo rails-vanilla; return
fi
[ -f "$p/pubspec.yaml" ] && echo flutter && return
[ -f "$p/package.json" ] && grep -q '"next"' "$p/package.json" && echo nextjs && return
[ -f "$p/package.json" ] && echo spa-web && return
{ [ -f "$p/Package.swift" ] || ls "$p"/*.xcodeproj >/dev/null 2>&1; } && echo ios-native && return
{ [ -f "$p/settings.gradle" ] || [ -f "$p/settings.gradle.kts" ]; } && echo android-native && return
echo unknown
}Map the result to a phase below. When in doubt, fetch the matching topic doc: curl https://docs.1pass.dev/llms/integrations/<stack>.md (rails, nextjs, flutter, swift, android, kotlin, react-native, spa-serverless, express).
Phase 2 — Drop-in wiring
Each snippet below is a minimal, dependency-light template. For the full reference (JWT verification, first-login mini-forms, account-merge handling, RP health checks), follow the linked integration guide.
2A. Rails (Hotwire / Inertia / vanilla)
Required env: LOGI_API_URL, LOGI_CLIENT_ID, LOGI_CLIENT_SECRET (Confidential client). For PKCE-only public clients, omit the secret and follow the Public Clients guide.
Controller — app/controllers/logi_sessions_controller.rb:
class LogiSessionsController < ApplicationController
def start
verifier = SecureRandom.urlsafe_base64(32).delete("=")
challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
state = SecureRandom.hex(16)
session[:logi_pkce] = verifier
session[:logi_state] = state
redirect_to "#{ENV['LOGI_API_URL']}/oauth/authorize?" + {
client_id: ENV["LOGI_CLIENT_ID"],
redirect_uri: logi_callback_url,
response_type: "code",
scope: ENV.fetch("LOGI_SCOPES", "openid profile:basic email"),
state: state,
code_challenge: challenge,
code_challenge_method: "S256",
}.to_query, allow_other_host: true
end
def callback
return render_error("state mismatch") if params[:state] != session[:logi_state]
res = Net::HTTP.post_form(
URI("#{ENV['LOGI_API_URL']}/oauth/token"),
grant_type: "authorization_code",
code: params[:code],
redirect_uri: logi_callback_url,
code_verifier: session[:logi_pkce],
client_id: ENV["LOGI_CLIENT_ID"],
client_secret: ENV["LOGI_CLIENT_SECRET"]
)
tokens = JSON.parse(res.body)
session.delete(:logi_pkce); session.delete(:logi_state)
# → resolve the user from the id_token / userinfo `sub`, then sign them in
# using your app's existing session helper. logi sends sub + email (+ nickname).
# For first-login extra fields use a mini signup form, do NOT auto-create blindly.
redirect_to after_sign_in_path
end
endRoutes — config/routes.rb:
get "auth/logi/start", to: "logi_sessions#start", as: :logi_start
get "auth/logi/callback", to: "logi_sessions#callback", as: :logi_callbackLogin button — Hotwire/Turbo must disable Turbo Drive (it silently swallows the cross-origin 302):
<%= link_to "Continue with logi", logi_start_path, data: { turbo: false }, rel: "external" %>Inertia + Svelte:
<a href="/auth/logi/start" data-turbo="false" rel="external" class="flex items-center gap-2">
Continue with logi
</a>return_to guard. If your post-login redirect honors a
return_toparam, reject protocol-relative URLs to avoid open redirects:rubydef sanitize_return_to(v) v = v.to_s return nil if v.blank? || !v.start_with?("/") || v.start_with?("//", "/\\") v end
Full reference (JWT verification, account-merge canonical_sub, webhook receiver, RP health endpoint): https://docs.1pass.dev/integrations/rails.
2B. Next.js (App Router)
Server-side: logi exposes OIDC discovery at /.well-known/openid-configuration, so next-auth / auth.js / openid-client configure from issuer: 'https://api.1pass.dev' alone:
providers: [{
id: "logi", name: "logi", type: "oauth",
wellKnown: "https://api.1pass.dev/.well-known/openid-configuration",
clientId: process.env.LOGI_CLIENT_ID,
clientSecret: process.env.LOGI_CLIENT_SECRET,
authorization: { params: { scope: "openid profile:basic email" } },
idToken: true,
checks: ["pkce", "state"],
}]Env (.env.local): LOGI_API_URL, LOGI_CLIENT_ID, LOGI_CLIENT_SECRET, LOGI_REDIRECT_URI.
Hand-rolled route handlers (no auth library) and SPA/browser PKCE: https://docs.1pass.dev/integrations/nextjs.
2C. Browser SPA (no backend)
Use the published browser SDK — PKCE, state, token rotation, and typed LogiAuthError codes are built in:
npm install @logi-auth/browserimport { LogiAuth } from "@logi-auth/browser";
export const auth = new LogiAuth({
clientId: import.meta.env.VITE_LOGI_CLIENT_ID, // public client, PKCE-only — no secret
redirectUri: `${window.location.origin}/auth/callback`,
});Register the app as Public (PKCE-only) — never ship a client_secret to the browser. Full flow: https://docs.1pass.dev/integrations/spa-serverless.
2D. Flutter
# pubspec.yaml
dependencies:
flutter_web_auth_2: ^4.0.0
flutter_secure_storage: ^9.0.0Register the callback custom scheme in Info.plist (iOS) and an intent filter for flutter_web_auth_2.CallbackActivity (Android).
First-try (recommended): before falling back to flutter_web_auth_2.authenticate(), try opening the authorize URL with url_launcher's LaunchMode.externalApplication. If the logi app is installed it opens directly (Naver/Kakao-style); otherwise the web-auth session handles it. Full guide + manifest blocks: https://docs.1pass.dev/integrations/flutter.
2E. iOS Native (Swift)
// Package.swift — use the published SDK (pin an exact version)
dependencies: [
.package(url: "https://github.com/dcode-co/logi-auth-swift", exact: "0.2.0"),
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "LogiAuth", package: "logi-auth-swift"),
]
),
]LogiAuth.configure(LogiAuthConfig(
clientId: "logi_xxx",
redirectURI: URL(string: "https://yourapp.com/oauth/callback")! // or a custom scheme
))
// Forward callbacks to the SDK — BOTH are required for app-to-app handoff:
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
if let url = activity.webpageURL { _ = LogiAuth.handle(url) }
}
.onOpenURL { url in _ = LogiAuth.handle(url) }
Button("Continue with logi") { Task { try await LogiAuth.signIn() } }Required Info.plist + Associated Domains:
LSApplicationQueriesSchemesmust include the logi app scheme (otherwise the first-try probe always reports "not installed").- Host your AASA file and enable
applinks:<your-app-host>— must not collide with the IdP host (api.1pass.dev), or you get spuriousmissingCodeerrors. Prefer a custom scheme callback to avoid host collisions.
Anti-pattern (native only)
// ❌ NEVER assume "standard OAuth PKCE" and use a bare web-auth session:
let s = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: "myapp") { ... }
s.start() // the logi app will NOT open even when installedlogi's mobile UX is a first-try app-to-app handoff. Treating it as plain RFC 6749/7636 fails the native experience. Always use LogiAuth (or the documented first-try coordinator). Reference: https://docs.1pass.dev/integrations/swift.
2F. Android Native (Kotlin)
fun signInWithLogi(activity: Activity) {
val authUrl = "https://api.1pass.dev/oauth/authorize?...".toUri()
try {
val intent = Intent(Intent.ACTION_VIEW, authUrl).apply {
setPackage("dev.onepass.app") // first-try the logi app
addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT) // API 34+
}
activity.startActivity(intent)
} catch (e: ActivityNotFoundException) {
CustomTabsIntent.Builder().build().launchUrl(activity, authUrl) // fallback
}
}Host .well-known/assetlinks.json (release + debug SHA256 fingerprints) for App Links. Full guide: https://docs.1pass.dev/integrations/android (or the published https://github.com/dcode-co/logi-auth-android).
Phase 3 — Register your app (get client_id / client_secret)
You register through the developer console (no internal API access needed). Two paths:
Option A — Developer console (UI)
- Sign up / sign in:
https://start.1pass.dev/developer/applications - Verify your email first — email-verified accounts only may register RPs.
- Create an application:
- App name — shown on the consent screen (avoid placeholder names like
test/sample, which trip the risk queue). - Client type — Confidential (has a backend) or Public (PKCE-only, mobile/SPA).
- Redirect URIs — exact match, e.g.
https://app.example.com/auth/logi/callback(multiple allowed).
- App name — shown on the consent screen (avoid placeholder names like
- Enable Scopes under the app's Scopes tab:
openid,profile:basic,email. - Copy the
client_id(logi_..., public-safe) and, for Confidential clients, theclient_secret— shown once. If lost, use Regenerate secret (old one is invalidated immediately).
Option B — logi CLI (automation-friendly)
logi login # first time only
logi apps create \
--name "$PROJECT" \
--redirect-uri "$SERVICE_URL/auth/logi/callback" \
--scope "openid profile:basic email"
# prints client_id and client_secret (secret shown once)Notes
- localhost /
*.staging.*/*.test.*redirect URIs auto-approve instantly. External production domains start asstatus: pendingand require a production-tier review (see Production 승급 in the registering guide). - Redirect URIs are exact-match (byte-for-byte; query/fragment/trailing-slash differences are rejected) and wildcards are unsupported (
*.vercel.appwon't work — register a separate sandbox app for previews). - iOS / Android / Web should each register a separate
client_idper platform.
Adding a redirect URI to an existing app
If an already-registered RP needs a new callback URL (a web surface added to a mobile app, a staging domain, etc.), add it rather than replace — existing URIs must stay registered or in-flight sessions break with invalid_request: redirect_uri not registered. Do this in the console (app → Edit → Redirect URIs) or:
logi apps update $CLIENT_ID --add-redirect-uri "$SERVICE_URL/auth/logi/callback"For a brand callback-path rename (e.g. /auth/1pass/callback → /auth/logi/callback) do a zero-downtime cutover: add the new path on the IdP first (keep both), deploy the app, drain old-path traffic, then remove the old path.
Phase 4 — Inject env vars on your host
Set these on your own deployment platform (Render dashboard, vercel env, fly secrets set, Heroku config vars, your .env, etc.). Use placeholder names below and fill in your real values:
| Key | Value |
|---|---|
LOGI_API_URL | https://api.1pass.dev |
LOGI_CLIENT_ID | logi_xxx |
LOGI_CLIENT_SECRET | logi_secret_xxx (Confidential clients only — server-side, never in client bundles) |
LOGI_SCOPES | openid profile:basic email |
When updating env vars, prefer per-key updates over a full "replace all" operation — replacing the whole set can wipe unrelated vars.
Phase 5 — Commit & deploy
Commit the new controller/routes/SDK wiring and the login-button change, then deploy. On most platforms an env-var change triggers a rebuild; if not, deploy manually so the new LOGI_* vars are picked up.
Phase 6 — Verify the live flow
Web (server-side) flow — the /start route should 302 to the IdP:
curl -sIL -H "User-Agent: Mozilla/5.0" \
"$SERVICE_URL/auth/logi/start" | grep -iE "^(HTTP|location)"
# expect: HTTP/2 302
# location: https://api.1pass.dev/oauth/authorize?client_id=logi_xxx&...Host check (the most common failure). The location must point at api.1pass.dev. If it falls back to localhost:3000, LOGI_API_URL was not injected — a 302 still returns, so checking only the status code misses it:
curl -sI "$SERVICE_URL/auth/logi/start" | grep -i "^location:" | grep -q "api.1pass.dev" \
&& echo "✓ LOGI_API_URL OK" || echo "✗ LOGI_API_URL missing"OIDC discovery sanity check:
curl -sI "$LOGI_API_URL/.well-known/openid-configuration" | head -1 # expect 200Then click "Continue with logi" in a browser → consent → callback → you should be signed in. If a user can't complete login (in-app browsers, Universal Link issues), point them at https://api.1pass.dev/diagnose for self-service diagnostics.
CLI verification (if using the logi CLI):
logi apps verify $CLIENT_ID -r "$SERVICE_URL/auth/logi/callback"
# checks: status approved / tier / redirect_uri registeredIn-app browser escape (all platforms)
In-app browsers (KakaoTalk, NAVER, Facebook, Instagram, Line, WeChat, TikTok, X/Twitter) ignore Universal Links, so the logi app never opens. Detect these via User-Agent on your login page and show an "Open in external browser" CTA. Reference patterns and the 8-environment escape guide: https://docs.1pass.dev/oauth/public-clients and the user-facing https://api.1pass.dev/diagnose.
RP health check (optional)
logi can send an hourly HMAC-signed GET /.well-known/logi-rp-health to your RP; echoing your client_id marks the app 🟢 in the console. Enable the toggle in the console to receive LOGI_RP_HEALTH_SECRET (set it as a server-side env var, never in client bundles). Per-stack handler templates and a self-verification curl are in the integration guides (e.g. https://docs.1pass.dev/integrations/rails#health-check-endpoint).
Reference links
- AI-assistant integration index:
https://docs.1pass.dev/guide/ai-assistants - Quickstart (manual, 5-minute):
https://docs.1pass.dev/guide/quickstart - Registering apps:
https://docs.1pass.dev/guide/registering-apps - Troubleshooting (failure scenarios + env checklist):
https://docs.1pass.dev/guide/troubleshooting - Scopes reference:
https://docs.1pass.dev/oauth/scopes - Public/PKCE clients:
https://docs.1pass.dev/oauth/public-clients