{"id":68424,"plugin_id":"plugin_asdk_app_6ae0640e1ddc8191851d134bfeb36884","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-28T13:15:20.273Z","digest":"4f1a8bd387545f64334c54abb9b5799885b88983004e3ebb5b53316a49973f25","against":null,"payload":{"description":"Register an org-scoped external interface app (a public/PKCE Auth0 OAuth client) for a custom UI, such as a Lovable-, v0- or bolt.new-built app, using Port's MCP tools, then wire the Authorization Code + PKCE grant into that UI so it can sign users in and call the Port API. Use when asked to 'build a custom UI on top of Port', 'connect my Lovable app to Port', 'register an OAuth app in Port', 'add Port login to my app', 'get a client ID for my external app', or 'fix a Callback URL mismatch / Service not found error when logging in with Port'. Out of scope: Port's own plugin and custom widget system (this skill never builds or installs anything inside Port itself), and managing Port's SSO or identity-provider connections.","included_files":[{"relative_path":"assets/login-page-anchor-ui.md","size_in_bytes":1473},{"relative_path":"assets/port-proxy-fallback.md","size_in_bytes":2213},{"relative_path":"assets/spa-example.md","size_in_bytes":25493},{"relative_path":"references/pkce-flow.md","size_in_bytes":7504},{"relative_path":"references/popup-login-explained.md","size_in_bytes":2916},{"relative_path":"references/registering-the-app.md","size_in_bytes":4841},{"relative_path":"references/troubleshooting.md","size_in_bytes":12728}],"name":"port-create-custom-oauth-app","skill_md_contents":"---\nname: port-create-custom-oauth-app\ndescription: \"Register an org-scoped external interface app (a public/PKCE Auth0 OAuth client) for a custom UI, such as a Lovable-, v0- or bolt.new-built app, using Port's MCP tools, then wire the Authorization Code + PKCE grant into that UI so it can sign users in and call the Port API. Use when asked to 'build a custom UI on top of Port', 'connect my Lovable app to Port', 'register an OAuth app in Port', 'add Port login to my app', 'get a client ID for my external app', or 'fix a Callback URL mismatch / Service not found error when logging in with Port'. Out of scope: Port's own plugin and custom widget system (this skill never builds or installs anything inside Port itself), and managing Port's SSO or identity-provider connections.\"\nlicense: MIT\ncompatibility: \"Claude Code, Cursor, Codex CLI, GitHub Copilot\"\nallowed-tools:\n  - list_external_interface_apps\n  - upsert_external_interface_app\n  - revoke_external_interface_app\nmetadata:\n  version: \"1.0.0\"\n  author: port-labs\n  repository: https://github.com/port-labs/port-skills\n  tags: port,oauth,pkce,auth0,external-interface,custom-ui,mcp-powered\n  summary: Register an OAuth app for a custom UI and wire up PKCE login\n---\n\n# Port custom OAuth app\n\nRegister an org-scoped **public/PKCE** OAuth client, an *external interface\napp*, so a custom UI you build outside Port can authenticate its own users\nand call the Port API directly. There is never a `client_secret`: these are\nPKCE public clients by design, so never ask for or invent one.\n\nRegistering, updating, and revoking apps takes **org admin** rights.\n\n## Prerequisites\n\n- Go over the `port-getting-started` skill first if this is your first time\n  working with Port.\n- Port's MCP server connected, as an org admin. The three tools above are\n  the only way this skill documents for registering an app, so there is no\n  non-MCP fallback here. Without them, search `search_port_knowledge_sources`\n  or [docs.port.io](https://docs.port.io) for the current alternative rather\n  than guessing an endpoint.\n- Register and test against the **published**, publicly reachable URL of the\n  UI, not a login-gated live preview, or the login redirect never lands.\n\n## How to set it up\n\n1. **Collect the inputs, never guess them:** app name, 1 to 5 exact redirect\n   URIs, and the Port region (EU, US, or other) that decides the Auth0 domain\n   and API base URL. See\n   [references/registering-the-app.md](references/registering-the-app.md).\n2. **Check existing apps, then register:** call\n   `list_external_interface_apps`, then `upsert_external_interface_app`\n   without `appId`. Keep `id` and `clientId` straight, they are not\n   interchangeable. Same reference.\n3. **Wire the PKCE flow into the UI:** popup-based login, `code_verifier`\n   kept in the opener's memory (never in `state` or a URL), token exchange\n   from a server route, `audience` set to the region's API base. See\n   [references/pkce-flow.md](references/pkce-flow.md). A complete React\n   example is in [assets/spa-example.md](assets/spa-example.md), and\n   [references/popup-login-explained.md](references/popup-login-explained.md)\n   explains why the popup flow is built the way it is.\n4. **Call the Port API** with the access token, and account for the CORS\n   allowlist's propagation delay. See\n   [references/pkce-flow.md#calling-the-port-api](references/pkce-flow.md#calling-the-port-api).\n5. **Check the code against the verification checklist** before telling the\n   user it is done. See\n   [references/pkce-flow.md#verification-checklist](references/pkce-flow.md#verification-checklist).\n6. **Add the published URL as a redirect URI** once the UI is published, and\n   manage or revoke apps later. See\n   [references/registering-the-app.md#managing-apps](references/registering-the-app.md#managing-apps).\n\n## Common pitfalls\n\nMore symptoms and the common mistakes to push back on are in\n[references/troubleshooting.md](references/troubleshooting.md).\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| `Callback URL mismatch` loop on login | `redirect_uri` is not an exact match for a registered URI, or it is a login-gated preview origin | Read `window.location.origin` from the running app and register that exact origin and path, and test from the published URL |\n| `Service not found: <audience>` | Wrong `audience` for the environment | Use the region's API base as the audience, never production's by default |\n| `/oauth/token` fails with a bare `Failed to fetch` | Auth0 CORS-blocks the browser call, the origin is not in the app's Allowed Web Origins | Do the token exchange from a server route |\n| `404: User with id \"me\" was not found` | Port has no `/v1/users/me` | Read claims from the decoded `id_token`, or call `/v1/users/{email}` |\n\n## Quick reference\n\n- Tools: `list_external_interface_apps`, `upsert_external_interface_app`,\n  `revoke_external_interface_app`. All need org admin.\n- `redirectUris`: 1 to 5, exact, absolute, no wildcards, `https://` only\n  (`http://` allowed for `localhost` / `127.0.0.1`). An update **replaces**\n  the whole set.\n- `id` goes to `appId`. `clientId` goes in the frontend as `client_id`.\n- Full detail: [registering-the-app](references/registering-the-app.md),\n  [pkce-flow](references/pkce-flow.md),\n  [popup-login-explained](references/popup-login-explained.md),\n  [troubleshooting](references/troubleshooting.md).\n\n## Examples\n\n- [assets/spa-example.md](assets/spa-example.md): complete React SPA login\n  flow, with the minimal `LoginPage`.\n- [assets/login-page-anchor-ui.md](assets/login-page-anchor-ui.md): the\n  default styled `LoginPage` using `@port-labs/anchor-ui`.\n- [assets/port-proxy-fallback.md](assets/port-proxy-fallback.md): same-origin\n  proxy for when the calling origin isn't a registered redirect URI.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}