# Neurobase > Neurobase is an open-source, local-first memory layer that captures, curates, and recalls durable context across Claude Code and Codex CLI. Neurobase records useful coding-agent session context with deterministic local hooks, folds it into a small current fact set, synthesizes a wikilinked Markdown wiki, and recalls relevant memory in later sessions. Its recommender mines recurring cross-agent patterns and proposes reusable SKILL.md, AGENTS.md, or CLAUDE.md instructions for human review. Current status: 0.1.0 release candidate. The working implementation is in GitHub; the first public Git tag, GitHub Release, and PyPI publication are still pending. ## Start Here - [Neurobase home](https://neurobase.dev/) - [For AI agents](https://neurobase.dev/for-agents) - [GitHub repository](https://github.com/rebelscum216/neurobase) - [README](https://github.com/rebelscum216/neurobase/blob/main/README.md) - [How Neurobase works](https://github.com/rebelscum216/neurobase/blob/main/docs/how-it-works.md) - [Architecture](https://github.com/rebelscum216/neurobase/blob/main/docs/architecture.md) - [Adapter guide](https://github.com/rebelscum216/neurobase/blob/main/docs/adapter-guide.md) - [Security](https://github.com/rebelscum216/neurobase/blob/main/SECURITY.md) - [Changelog](https://github.com/rebelscum216/neurobase/blob/main/CHANGELOG.md) ## Articles - [All articles](https://neurobase.dev/articles) - [Machine-readable article catalog](https://neurobase.dev/articles.json) - [RSS feed](https://neurobase.dev/feed.xml) - [JSON Feed](https://neurobase.dev/feed.json) Each article also has a clean Markdown version at its canonical URL plus `.md`. ## Core Concepts - Local-first storage - Cross-agent memory - Deterministic capture hooks - LLM-based curation that supersedes and removes stale facts - Wikilinked Markdown synthesis - Automatic recall at session start - On-demand recall over MCP - Human-reviewed skill and rule recommendations ## Installation Until the first public package release: ```bash git clone https://github.com/rebelscum216/neurobase.git cd neurobase uv tool install . neurobase init --user neurobase doctor ``` ## Optional - [Expanded Neurobase corpus for agents](https://neurobase.dev/llms-full.txt) ## Verified Product Facts - Package: neurobase-cli - Command: neurobase - Version: 0.1.0 (release candidate) - Python: >=3.11 - License: Apache-2.0 - Privacy: Local-only. Zero telemetry. No cloud dependency. - Native adapters: Claude Code, Codex CLI ## Article Catalog ### How to Connect Outlook to Claude with a Secure Remote MCP Server - Published: 2026-07-22 - Modified: 2026-07-22 - Summary: An architecture and implementation guide for connecting Claude to Microsoft 365 through a narrowly scoped remote MCP server, Microsoft Entra ID, and Microsoft Graph. - Topics: Model Context Protocol, Claude, Microsoft Outlook, Microsoft Graph, OAuth - Canonical: https://neurobase.dev/articles/connect-outlook-to-claude-with-remote-mcp - Markdown: https://neurobase.dev/articles/connect-outlook-to-claude-with-remote-mcp.md ### Your MCP Works for You—but Not Your Coworkers: The Azure Allowlist Trap - Published: 2026-07-22 - Modified: 2026-07-22 - Summary: A troubleshooting guide to the overlapping Entra assignment and Azure Container Apps authorization lists that can reject valid remote MCP users before requests reach application logs. - Topics: Model Context Protocol, Azure Container Apps, Microsoft Entra ID, OAuth, Troubleshooting - Canonical: https://neurobase.dev/articles/mcp-works-for-you-but-not-your-coworkers - Markdown: https://neurobase.dev/articles/mcp-works-for-you-but-not-your-coworkers.md ### Debug Remote MCP Authentication with Postman Before Blaming Claude - Published: 2026-07-22 - Modified: 2026-07-22 - Summary: A protocol-first debugging ladder for proving remote MCP reachability, discovery metadata, PKCE authorization, tokens, initialization, tool discovery, and tool execution. - Topics: Model Context Protocol, Postman, OAuth, PKCE, Troubleshooting - Canonical: https://neurobase.dev/articles/debug-remote-mcp-authentication-with-postman - Markdown: https://neurobase.dev/articles/debug-remote-mcp-authentication-with-postman.md ### Salesforce External Client Apps vs. Connected Apps: What Changed in Spring ’26 - Published: 2026-07-22 - Modified: 2026-07-22 - Summary: A practical comparison of Salesforce External Client Apps and Connected Apps, including Spring ’26 creation changes, configuration ownership, OAuth choices, and migration guidance. - Topics: Salesforce, External Client Apps, Connected Apps, OAuth, Spring 26 - Canonical: https://neurobase.dev/articles/salesforce-external-client-apps-vs-connected-apps - Markdown: https://neurobase.dev/articles/salesforce-external-client-apps-vs-connected-apps.md ### How to Build a Read-Only Salesforce Dashboard with an External Client App - Published: 2026-07-22 - Modified: 2026-07-22 - Summary: An implementation guide for a user-authenticated Salesforce dashboard using an External Client App, Authorization Code with PKCE, a backend-for-frontend, bounded SOQL queries, and no write operations. - Topics: Salesforce, External Client Apps, OAuth, PKCE, Read-only dashboards - Canonical: https://neurobase.dev/articles/build-a-read-only-salesforce-dashboard-with-an-external-client-app - Markdown: https://neurobase.dev/articles/build-a-read-only-salesforce-dashboard-with-an-external-client-app.md Complete catalog: https://neurobase.dev/articles.json --- # How to Connect Outlook to Claude with a Secure Remote MCP Server Canonical: https://neurobase.dev/articles/connect-outlook-to-claude-with-remote-mcp Markdown: https://neurobase.dev/articles/connect-outlook-to-claude-with-remote-mcp.md An architecture and implementation guide for connecting Claude to Microsoft 365 through a narrowly scoped remote MCP server, Microsoft Entra ID, and Microsoft Graph. Most Outlook-and-AI demos begin at the fun part. Someone asks, “What did Sarah say about the renewal?” and a tidy answer appears three seconds later. The interesting part is everything hiding behind that answer. Claude needs a safe way to reach Microsoft 365. Microsoft Graph needs to know which mailbox the application may access. Your company needs a way to decide who may use the connection. And nobody should paste a client secret into a desktop configuration file and hope for the best. That is where a remote Model Context Protocol server—or remote MCP server—fits. It sits between Claude and Microsoft Graph, exposing a small set of email tools while keeping Microsoft credentials, permissions, and business rules on infrastructure you control. This guide explains the architecture and the setup sequence. The goal is not merely to make one successful email search. It is to build a connector that you can understand, test, and safely give to another person. > **Visual placeholder:** A 6–8 second opening GIF. In Claude, ask “Summarize the latest messages about Project Atlas.” Claude calls the Outlook connector and returns a short summary with message dates and senders. Use a demo mailbox and synthetic messages. ## What we are building The finished flow has four participants: 1. **Claude** decides when to use an email tool. 2. **Your remote MCP server** describes those tools and receives Claude’s requests. 3. **Microsoft Entra ID** authenticates the server and issues Microsoft Graph tokens. 4. **Microsoft Graph** reads the permitted mailbox data. The MCP server is the control point. It can expose narrow tools such as: - `search_messages(query, after_date, limit)` - `get_message(message_id)` - `list_recent_messages(folder, limit)` - `get_thread(message_id)` Notice what is not on that list: `send_message`, `delete_message`, or `move_message`. Start read-only. You can always add carefully controlled actions later; removing an overly powerful tool after people depend on it is harder. > **Visual placeholder:** Architecture diagram showing Claude → HTTPS/OAuth → Remote MCP Server → Microsoft Graph. Put Entra ID above the MCP-to-Graph connection and label it “app-only token.” ## Step 1: Decide whose identity reads the mail Microsoft Graph offers two broad access patterns. With **delegated access**, the app acts on behalf of a signed-in user. What it can do is constrained by both the app’s scopes and that user’s permissions. With **app-only access**, the service acts as itself. No mailbox user has to remain signed in, which makes this useful for a shared mailbox, scheduled process, or centrally managed assistant. The tradeoff is important: application permissions can be broad, so an administrator must grant consent and should restrict the app to the intended mailbox population. For a remote MCP server serving one shared or functional mailbox, app-only access is often the simpler operational model. For a personal assistant where every employee should see only their own mail, delegated access is usually a better fit. Do not choose based on which sample code is shortest. Choose based on the identity and access rule you want to be true six months from now. ## Step 2: Register the Microsoft Entra application In the Microsoft Entra admin center, create an app registration for the Graph-facing service. Record the tenant ID and application ID. Then add only the Microsoft Graph application permissions your MCP tools require. A connector that searches and reads messages needs read access; it does not need permission to send email simply because sending might be useful someday. An administrator must grant tenant-wide consent for application permissions. This is a good moment to involve the Microsoft 365 or identity administrator instead of treating them as the final boss encountered at deployment time. For an early prototype, a client secret is convenient. For production, prefer a certificate, workload identity, or managed identity where your hosting platform supports it. Secrets expire, get copied, and have an unfortunate habit of becoming “temporary” for several years. > **Visual placeholder:** Screenshot sequence from Entra: App registrations → New registration → API permissions. Highlight the application-permission column and the admin-consent status. Blur all tenant and application identifiers. ## Step 3: Restrict mailbox access This is the step that separates a responsible enterprise tutorial from “congratulations, your demo can read the company directory.” Microsoft Graph application permissions describe what the app may do. Exchange authorization should further restrict where it may do it. For new configurations, Microsoft recommends **Role Based Access Control for Applications in Exchange Online**, which can pair an application role such as `Application Mail.Read` with a resource scope containing only the intended mailboxes. Application Access Policies are now the legacy path. One detail can defeat an otherwise careful scope: Exchange application-RBAC grants and unscoped Microsoft Entra application permissions are additive. If the service principal still has tenant-wide `Mail.Read` through Entra, adding a mailbox-scoped Exchange role does not narrow that existing grant. Remove the unscoped permission when moving the authorization into scoped Exchange RBAC. The principle is stable even when the administration screen changes: create an explicit mailbox scope, add the one or few mailboxes the connector needs, and test both an allowed and a denied mailbox. Your negative test matters. A successful read proves that the app works. A rejected read proves that the boundary works. > **Visual placeholder:** Split-screen result. Left: permitted demo mailbox returns messages. Right: an out-of-scope demo mailbox returns a deliberate access-denied result. ## Step 4: Build small Graph-backed tools Inside the MCP server, acquire a Graph token using a supported Microsoft identity library. For app-only authentication, request the `https://graph.microsoft.com/.default` scope; the token will contain the application roles already granted to the Entra app. Then keep each MCP tool boring and explicit. A search tool should validate its inputs, cap the result count, select only the fields Claude needs, and return stable identifiers that another tool can use. A message-reading tool should accept one identifier and return a clean text representation rather than dumping the entire Graph response into the model’s context. This is not only a performance improvement. Smaller results reduce accidental exposure of irrelevant recipients, headers, and attachments. They also make Claude’s answers better because the important text is not buried in metadata. Useful response fields usually include: - Subject - Sender and recipients - Sent or received time - A plain-text body or carefully converted HTML body - A stable message or conversation ID - A browser link back to Outlook, if appropriate Never log access tokens or full message bodies by default. Log the tool name, request correlation ID, timing, result count, and a sanitized error category. ## Step 5: Put OAuth in front of the MCP server Microsoft Graph authentication protects the downstream mailbox. You still need to control who can connect Claude to your MCP server. Claude’s remote custom connectors support OAuth. Your server therefore needs to participate in the MCP authorization flow: advertise its protected-resource metadata, direct the client to an authorization server, validate access tokens on the MCP endpoint, and handle expiry and refresh correctly. In an enterprise deployment, Microsoft Entra ID is a natural authorization server. Some architectures can use it directly. Others use a small OAuth broker that presents the client-facing authorization endpoints and delegates sign-in to Entra. The broker pattern is useful when the client behaves like a public PKCE client but your Entra configuration or downstream service also requires confidential-client behavior. Keep these two identities separate in your head: - **The employee connecting Claude to the MCP server** - **The service identity used by the MCP server to call Graph** They may both involve Entra, but they answer different questions: “Who may use this connector?” and “What mailbox data may the service read?” > **Visual placeholder:** Animated sequence diagram. User clicks Connect in Claude → browser opens Entra sign-in → callback returns to Claude → Claude calls `/mcp` with an access token → server calls Graph with its service token. ## Step 6: Deploy it somewhere Claude can reach A remote MCP server needs a stable public HTTPS URL. Azure Container Apps is a sensible choice if the rest of your identity and data live in Microsoft’s ecosystem, but the same principles apply to other hosts. Configure: - HTTPS ingress - A health endpoint that does not reveal configuration details - Secrets or identity bindings outside the image - Structured application logs - Reasonable request timeouts - At least one rollback-ready image or revision If you build the container on an Apple Silicon Mac, explicitly target the architecture expected by the hosting environment. An image can build perfectly and then fail instantly in the cloud because it contains the wrong machine architecture—a surprisingly sophisticated way to produce a very short log. ## Step 7: Add the connector to Claude In Claude, open **Settings → Connectors** and add a custom connector using the remote MCP URL. On Team or Enterprise plans, an owner may first need to add or enable the connector for the organization; users then connect their individual accounts. Complete the OAuth flow, enable only the email tools you intend to test, and start with deterministic prompts: - “List the five most recent messages in the inbox.” - “Find messages received after July 1 containing ‘Project Atlas.’” - “Open the newest result and return its sender, date, and first paragraph.” These are better tests than “What should I know?” because each failure points to a narrower layer. > **Visual placeholder:** Screenshot series showing Add custom connector, the OAuth consent flow, enabled tool list, and the first successful call. ## The verification checklist Before calling the connector finished, verify all of the following: - A permitted user can add and authenticate the connector. - A user who is not assigned cannot use it. - The server can read the intended mailbox. - The server cannot read an out-of-scope mailbox. - Claude discovers only the tools you meant to expose. - Large searches are capped and paginated. - Tokens and message bodies do not appear in routine logs. - Expired tokens refresh or fail with an understandable recovery path. - Removing a user or mailbox from an allowlist actually revokes access. The flashy moment is Claude summarizing an inbox. The real accomplishment is knowing exactly why it was allowed to do so—and knowing that the same request against the wrong mailbox or by the wrong user will fail. That is the difference between an AI demo and an enterprise integration. ## References - [Anthropic: Getting Started with Custom Connectors Using Remote MCP](https://support.anthropic.com/en/articles/11175166-about-custom-integrations-using-remote-mcp) - [Anthropic: Building Custom Connectors via Remote MCP Servers](https://support.anthropic.com/en/articles/11503834-building-custom-integrations-via-remote-mcp-servers) - [Microsoft: Get access without a user](https://learn.microsoft.com/en-us/graph/auth-v2-service) - [Microsoft: Choose a Microsoft Graph authentication provider](https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers) - [Microsoft: Role Based Access Control for Applications in Exchange Online](https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac) --- # Your MCP Works for You—but Not Your Coworkers: The Azure Allowlist Trap Canonical: https://neurobase.dev/articles/mcp-works-for-you-but-not-your-coworkers Markdown: https://neurobase.dev/articles/mcp-works-for-you-but-not-your-coworkers.md A troubleshooting guide to the overlapping Entra assignment and Azure Container Apps authorization lists that can reject valid remote MCP users before requests reach application logs. There is a special category of authentication bug designed to make a competent developer question every layer of the internet. The connector works for you. It works repeatedly. The OAuth screen appears, the callback completes, and Claude can use the tools. Then a coworker tries the same connector and gets a vague failure. Your application logs show nothing—not an error, not a rejected request, not even proof that Claude called the server. Naturally, you investigate Claude. Then the OAuth broker. Then token scopes, callback URLs, browser state, enterprise settings, and whether your coworker clicked the correct button on a Tuesday. The actual problem may be one layer earlier: Azure Container Apps authentication can reject the request before your application ever sees it. This is the story of that trap, how to recognize it, and how to avoid losing several days to a perfectly healthy application. > **Visual placeholder:** Opening animation. Two people add the same MCP connector. “Developer” gets a green success result; “Coworker” gets a red connection failure. The server’s application-log panel stays blank. ## The architecture that creates the mystery Imagine a remote MCP server deployed on Azure Container Apps. The application implements its own OAuth endpoints and validates bearer tokens at `/mcp`. Azure’s built-in authentication feature—often called Easy Auth—is also enabled in front of the container. Your request path is therefore not simply: `Claude → your application` It is: `Claude → Azure authentication sidecar → your application` That sidecar is useful. It can validate Microsoft Entra tokens, establish a user principal, and enforce an authorization policy without requiring your application to reimplement every platform concern. It can also deny a valid request before Express, FastAPI, or whatever runs your MCP server receives a single byte. That is why the empty application logs are not evidence that Claude never called your URL. They may be evidence that an upstream gate stopped the request. > **Visual placeholder:** Layer diagram with a red stop sign between Claude and the application. Label the stop sign “Easy Auth authorization policy — 403 before app code.” ## The misleading setting: Allow Anonymous The configuration name `unauthenticatedClientAction: AllowAnonymous` looks comfortingly decisive. It is tempting to read it as “Easy Auth is not enforcing access; my app handles authentication.” That interpretation is incomplete. The setting governs what happens when a request has no authenticated identity. A credential-less request may be allowed through to your app. But when a request includes a token that Azure can validate, the platform can establish a principal and then apply its default authorization policy. In the deployment that inspired this article, an authenticated principal missing from `allowedPrincipals`—and not covered by `allowedApplications`—received a 403 before the application ran. Azure exposes both lists under the Microsoft Entra provider’s `defaultAuthorizationPolicy`, even though they may not be obvious in the portal’s top-level authentication summary. So you can observe all three of these at once: - An unauthenticated health request reaches the application. - Your own authenticated MCP request reaches the application. - Your coworker’s valid authenticated request never reaches the application. No contradiction is required. The requests take different branches through the platform policy. ## The two-list problem In a typical enterprise setup, a new user may need to be approved in two places. First, the user needs access to the Microsoft Entra enterprise application. If user assignment is required, an administrator must assign the user or a group to the application. Second, the same user may need to appear in the Azure Container App authentication policy’s `allowedPrincipals` identities list—or be covered by an equivalent allowed group or application rule. The effective audience is the intersection: `People assigned to the Entra app ∩ People allowed by the Container App policy` Being on either list alone is not enough. This is particularly easy to miss when the two permissions have different owners. An identity administrator controls enterprise-app assignment. An application owner controls the Container App. Each person can truthfully say, “The user is approved on my side.” The system can still reject the user. > **Visual placeholder:** Venn diagram. Left circle: “Entra app assignment.” Right circle: “Container App allowed principals.” The overlap is labeled “People who can actually connect.” ## Why it looks like a client-side failure Remote MCP adds another layer of indirection. The user is not manually sending a request to `/mcp`; Claude is doing it after an OAuth flow. The UI may summarize several possible failures as “connection failed” or “could not authenticate.” Meanwhile, your app logs show no request because the Azure sidecar produced the response. If you look only at application stdout, the most plausible story is that the client never called you. That story sends debugging in the wrong direction. The fix is to observe each boundary independently: 1. Did the authorization endpoint receive the initial request? 2. Did the token endpoint complete successfully? 3. Was an access token issued with the expected issuer, audience, and scopes? 4. Did Azure ingress receive the subsequent MCP request? 5. Did the Easy Auth layer accept or reject its principal? 6. Did the application receive the request? Do not collapse “OAuth succeeded” and “the protected resource accepted the token” into one event. They are different requests and can fail under different policies. ## A fast diagnostic sequence When the connector works for one person but not another, resist the urge to change code. Compare identity and policy state first. ### 1. Confirm the failure is user-specific Have the working and failing users repeat the same deterministic operation. Avoid a broad Claude prompt that may or may not choose the tool. ### 2. Compare Entra assignment Check whether both identities are assigned to the enterprise application or covered by the same assigned group. Record object IDs carefully; display names and email addresses are friendly labels, not reliable policy keys. ### 3. Inspect the complete Container App auth configuration Read the live `authConfigs/current` resource and examine the Azure Active Directory provider’s validation and default authorization policy. Do not assume the portal summary exposes every consequential field. Look for both: - `allowedApplications` - `allowedPrincipals.identities` ### 4. Compare platform logs with application logs Application logs answer “What did my code do?” Platform and ingress diagnostics answer “Did the request get to my code?” You need both. ### 5. Add one user, then retest Make the smallest reversible policy change. If the user immediately connects without a new deployment, you have strong evidence that the platform allowlist—not the OAuth implementation—was the root cause. > **Visual placeholder:** Troubleshooting flowchart ending in “No app log entry + platform 403 → inspect Easy Auth defaultAuthorizationPolicy.” ## Do not casually edit the allowlist Authorization arrays deserve the same caution as firewall rules. A malformed update can lock out the people who were already working. Prefer a read-modify-write process: 1. Retrieve the current authentication configuration. 2. Preserve every unrelated setting and existing identity. 3. Add the new identity once. 4. Validate the resulting JSON structure. 5. Submit the complete updated resource. 6. Read it back and verify the stored values. 7. Test an existing user, the new user, and an unapproved user. Be careful with command-line convenience syntax that serializes array values. Quoting mistakes can store literal quote characters inside identity strings. The update command may report success while every entry becomes unusable. When access control is involved, “the command exited zero” is not verification. ## Make onboarding a single checklist Once you understand the two-list model, encode it in the operating procedure. A good user-onboarding checklist includes: - Entra enterprise-app assignment - Container App allowed-principal or allowed-group membership - Claude organization connector availability - Individual OAuth connection completed - Expected MCP tools enabled - One deterministic test call - A corresponding offboarding procedure for every list If two administrators own different steps, name both roles in the checklist. Better yet, use a shared group as the source of truth where your security model permits it. Manually synchronizing duplicate user arrays works right up until it does not. ## The larger lesson Modern cloud requests pass through a stack of helpful machinery: DNS, ingress, web application firewalls, platform authentication, your own authorization middleware, and downstream API permissions. Any one of those layers can reject a request. An empty application log does not prove the client was idle. It proves only that your application did not observe the request. When an integration works for its creator but not its users, draw the full request path and list every gate. Then compare the working and failing identities at each gate before rewriting code. Authentication bugs are still annoying. But they become much less mystical once every invisible bouncer has a name. ## References - [Microsoft: Authentication and authorization in Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/authentication) - [Microsoft: Container Apps Auth Config REST resource](https://learn.microsoft.com/en-us/rest/api/resource-manager/containerapps/container-apps-auth-configs/get?view=rest-resource-manager-containerapps-2025-07-01) - [Anthropic: Getting Started with Custom Connectors Using Remote MCP](https://support.anthropic.com/en/articles/11175166-about-custom-integrations-using-remote-mcp) --- # Debug Remote MCP Authentication with Postman Before Blaming Claude Canonical: https://neurobase.dev/articles/debug-remote-mcp-authentication-with-postman Markdown: https://neurobase.dev/articles/debug-remote-mcp-authentication-with-postman.md A protocol-first debugging ladder for proving remote MCP reachability, discovery metadata, PKCE authorization, tokens, initialization, tool discovery, and tool execution. Claude is a wonderful way to use an MCP server and a terrible way to debug one. That is not a criticism. Claude’s job is to interpret a request, decide whether a tool is useful, call it, and turn the result into a helpful answer. During debugging, those conveniences create ambiguity. Did OAuth fail? Did tool discovery fail? Did Claude decide not to call the tool? Did the tool return malformed content? Did the model simply misunderstand the result? If you begin with the full AI experience, five different layers can produce the same disappointing sentence. Postman gives you a more boring path. Boring is exactly what we want. The goal is to prove the protocol in small pieces—metadata, authorization, tokens, initialization, tool discovery, and tool execution—before adding model behavior. > **Visual placeholder:** Opening split-screen. Left: Claude displays a generic connector error surrounded by question marks. Right: Postman shows a precise 401 response with an `invalid_token` detail. Caption: “Mystery vs. evidence.” ## The debugging ladder Treat a remote MCP connection as a ladder. Do not climb to the next rung until the current one holds. 1. The server is reachable over HTTPS. 2. Protected-resource and authorization metadata are discoverable. 3. The OAuth authorization flow completes. 4. The token endpoint issues the expected token. 5. The MCP endpoint accepts that token. 6. MCP initialization succeeds. 7. The server lists its tools. 8. A tool returns a valid result. 9. Claude can connect and use it. This order matters. Testing step nine while step four is broken creates a highly advanced random-number generator. > **Visual placeholder:** Vertical ladder graphic with the nine rungs. Animate each rung turning green as the article progresses. ## Start with reachability, not OAuth Before configuring Postman authorization, verify that the hostname resolves, the TLS certificate is valid, and a deliberately public health endpoint responds. A health endpoint should be useful but uninteresting. Return a status and perhaps a version or build identifier. Do not expose environment variables, tenant IDs, configured secrets, or downstream connection details. Then send an unauthenticated request to the MCP endpoint. A 401 response is often good news. It proves that DNS, TLS, ingress, routing, and at least one authentication boundary are functioning. A timeout, 404, 502, and 401 are not interchangeable “it doesn’t work” outcomes. Preserve the distinction. ## Discover the server’s metadata OAuth-protected MCP servers use metadata to tell clients where authorization happens and how the protected resource is identified. Fetch those documents directly and inspect the values. Confirm that: - Every advertised URL is publicly reachable. - HTTPS is used consistently. - The authorization server issuer is exact. - Authorization and token endpoints point to the intended environment. - The protected resource matches the MCP endpoint’s expected audience. - Supported PKCE methods include what your client will use. Small URL differences cause large amounts of confusion. A missing path segment, stale deployment hostname, or production issuer paired with a development resource can allow the browser portion of OAuth to look successful while producing a token the API will never accept. > **Visual placeholder:** Postman screenshot of the metadata response. Add callouts around issuer, authorization endpoint, token endpoint, resource, and PKCE support. ## Run Authorization Code with PKCE deliberately For a public client, Authorization Code with Proof Key for Code Exchange prevents an intercepted authorization code from being redeemed without the original verifier. Postman can generate the verifier and challenge for you, but you should still understand the sequence: 1. The client creates a random code verifier. 2. It hashes that verifier into a code challenge. 3. The authorization request includes the challenge. 4. The user signs in and approves access. 5. The authorization server redirects back with a short-lived code. 6. The client exchanges the code plus the original verifier for tokens. Configure the callback URL exactly as registered with the authorization server. OAuth compares redirect URIs precisely for good reason; “almost the same URL” is not a category. During the first pass, capture the authorization response and token response without copying tokens into notes, screenshots, tickets, or chat. Postman can hold the token as an environment variable and apply it to later requests. > **Visual placeholder:** Short animation of the PKCE exchange. Show the challenge traveling outward and the hidden verifier returning at token exchange. Do not show a real token. ## Inspect the token’s claims safely If the token is a JWT, decode its header and payload locally or with a trusted internal tool. Decoding is not validation; it is only a way to inspect the claims. The claims that most often explain a rejection are: - `iss`: who issued the token - `aud`: which API the token is meant for - `exp` and `nbf`: when it is valid - `scp`: delegated scopes - `roles`: application roles - `oid` or `sub`: the calling identity - `tid`: the tenant Your server must still verify the signature using the issuer’s published keys and enforce issuer, audience, time, and permission requirements. A token that looks beautifully formatted can still be invalid. Create a small claim checklist for each environment. “Audience should be the MCP application ID” is more useful than repeatedly staring at a long GUID and waiting for enlightenment. ## Call the MCP endpoint yourself Now use the access token as a bearer token and initialize an MCP session. The exact request shape depends on the protocol version and transport supported by your server, so use the current MCP specification and SDK behavior as your source of truth. At this stage, save the raw requests and responses. Check: - HTTP status and content type - Protocol-version negotiation - Session identifiers or related headers - JSON-RPC request IDs - Error objects, not just error messages Once initialization succeeds, request the tool list. Tool discovery should be deterministic. You should see the name, description, and input schema for every tool you intended to publish—and no experimental or dangerous tools you forgot to hide. Then call the smallest possible tool. A `get_status` or tightly capped list operation is better than a broad semantic search backed by three external services. > **Visual placeholder:** Three Postman tabs labeled Initialize, List Tools, and Call Tool. Animate a green check appearing on each. ## Read failures by layer Here is the practical map I use: ### The browser never reaches sign-in Suspect authorization metadata, an invalid authorization URL, client registration, or redirect configuration. ### Sign-in succeeds but token exchange fails Suspect the callback URI, PKCE verifier, client authentication expectations, authorization-code reuse, or token-endpoint configuration. ### A token is issued but `/mcp` returns 401 Suspect signature validation, issuer, audience, expiry, or missing scopes and roles. ### `/mcp` returns 403 and the application has no log entry Suspect an upstream authorization layer such as Azure Container Apps Easy Auth, an ingress policy, or a web application firewall. ### Initialization works but tools are missing Suspect server registration, feature flags, protocol-version behavior, or tool schema construction. ### The direct tool call works but Claude does not use it Congratulations: you have finally earned the right to investigate Claude-side configuration, tool enablement, descriptions, and prompting. That last sentence is not sarcasm. Reaching it means you have removed most of the system from suspicion. ## Make Postman a permanent smoke test Do not discard the collection after the first successful connection. Sanitize it and keep it with the project as an operational artifact. A useful collection contains: - Health check - Protected-resource metadata - Authorization-server metadata - OAuth configuration using variables - Unauthenticated MCP request expecting 401 - Authenticated initialization - Tool listing - One harmless tool call - One deliberately forbidden operation or identity test Use environment variables for hostnames, client IDs, resource identifiers, and tokens. Exported collections should contain placeholders, never live credentials. Add a short README explaining which values are safe to share and which must remain local. This collection becomes valuable during deployments. If a new container revision fails, you can determine in minutes whether the problem is ingress, OAuth, protocol negotiation, or the downstream tool. That is much faster than asking the model progressively angrier versions of the same question. ## Then bring Claude back in Once Postman proves the end-to-end protocol, add the remote connector in Claude and authenticate it. Enable only the tool you already tested. Use a prompt that clearly requires that tool and asks for fields you know the direct response contains. If it works, expand gradually. If it fails, the remaining search space is small: connector configuration, Claude organization policy, individual tool enablement, or how clearly the tool description communicates its purpose. AI integrations feel unpredictable when protocol failures and model decisions are mixed together. Separate them. Make the network and authentication path boring first; then let the model be the interesting part. ## References - [Anthropic: Building Custom Connectors via Remote MCP Servers](https://support.anthropic.com/en/articles/11503834-building-custom-integrations-via-remote-mcp-servers) - [Model Context Protocol specification](https://modelcontextprotocol.io/specification/) - [Salesforce: Connecting an MCP Client](https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/client-connection-overview.html) --- # Salesforce External Client Apps vs. Connected Apps: What Changed in Spring ’26 Canonical: https://neurobase.dev/articles/salesforce-external-client-apps-vs-connected-apps Markdown: https://neurobase.dev/articles/salesforce-external-client-apps-vs-connected-apps.md A practical comparison of Salesforce External Client Apps and Connected Apps, including Spring ’26 creation changes, configuration ownership, OAuth choices, and migration guidance. If you have integrated anything with Salesforce in the last decade, you probably know the ritual: open Setup, create a Connected App, enable OAuth, choose scopes, retrieve the consumer key, and wait an oddly suspenseful number of minutes for the configuration to become usable. In Spring ’26, that default changed. Salesforce restricted the creation of new Connected Apps and now recommends External Client Apps for new integrations. Existing Connected Apps can continue working, so this is not a “drop everything and migrate by lunch” emergency. It is a change in the path forward—and it matters for every new dashboard, middleware service, mobile app, or AI connector you build. External Client Apps, or ECAs, are not merely Connected Apps wearing a new label. They reorganize who controls an integration’s settings, improve how configuration participates in modern development workflows, and begin from a more deliberately closed security posture. Let’s make the difference concrete. > **Visual placeholder:** Opening slide with the familiar Connected App setup screen sliding left and the External Client App Manager screen sliding in from the right. Caption: “Existing apps still work. New integrations have a new default.” ## First: what is either kind of app doing? Neither a Connected App nor an External Client App is usually “the application” your users interact with. It is Salesforce’s representation of an external integration. It tells Salesforce things such as: - Which OAuth flows the integration may use - Which callback URLs are valid - Which scopes it can request - How users are approved - How tokens should be handled - Which security policies apply The external dashboard running on Azure, the mobile application on a phone, or Claude connecting through MCP is the client. The Salesforce app configuration establishes the trust relationship. That distinction prevents a common misunderstanding: deleting your frontend does not automatically delete its Salesforce authorization configuration, and changing a callback URL in code does not update Salesforce. The two sides must agree. ## Why Salesforce introduced External Client Apps Connected Apps grew into a large and capable integration mechanism, but their configuration model mixes concerns that belong to different people. A software developer might define an OAuth flow, callback URL, and required scopes. A Salesforce administrator should decide which users may authorize the integration, how long tokens live, and what local security policies apply. Those decisions change at different times and travel through different processes. External Client Apps more clearly separate **developer-controlled settings** from **admin-controlled policies**. That matters when an integration is packaged or deployed across environments. The developer-owned definition can move through source control and deployment tooling, while each Salesforce org retains authority over its local access policy. Salesforce also describes ECAs as closed by default. An app is not simply available everywhere because its definition exists somewhere; it must be explicitly created, installed, or otherwise allowed in the target org according to its distribution model. In plain English: the integration definition can travel, but trust is still granted locally. > **Visual placeholder:** Two-column diagram. “Developer-controlled” contains flows, callback URLs, and scopes. “Admin-controlled” contains permitted users, profiles/permission sets, token policy, and local access. ## What changed in Spring ’26 Starting with Spring ’26, Salesforce disabled the ability to create new Connected Apps by default. Existing Connected Apps continue to function. Organizations that have a specific reason to create another one can contact Salesforce Support, but Salesforce recommends ECAs for new integrations and migration planning. That leads to three practical rules: 1. **Do not panic-migrate a healthy existing integration merely because the season changed.** Inventory it, understand it, and plan deliberately. 2. **Use an External Client App when starting a new integration.** Most new tutorials that still begin with “click New Connected App” are teaching the old default. 3. **Treat migration as security and release work, not a rename.** Consumer credentials, callbacks, policies, user assignments, and token behavior all deserve testing. > **Visual placeholder:** Decision tree. “Existing Connected App working?” → Yes: inventory and plan. “New integration?” → Create ECA. “Special legacy requirement?” → Evaluate support path. ## A side-by-side mental model Here is the comparison I find most useful: | Question | Connected App | External Client App | |---|---|---| | Is it still supported? | Existing apps continue to work | Yes; recommended for new integrations | | Can most orgs create a new one by default? | No, creation is restricted as of Spring ’26 | Yes, subject to permissions | | Security posture | Powerful legacy model with many settings | Designed around a closed-by-default model | | Configuration ownership | Developer and admin concerns are less cleanly separated | Developer settings and subscriber policies are explicitly separated | | Modern deployment workflow | Some metadata support, with historical limitations | Designed for metadata-driven development and packaging | | Good choice for a new OAuth integration? | Generally no | Yes | The important word is **model**. The OAuth standards underneath are familiar: authorization code, PKCE, client credentials, JWT bearer, refresh tokens, scopes, and callbacks. ECAs change how Salesforce represents and governs the client, not the basic reason OAuth exists. ## Creating a local External Client App For an integration used only in one Salesforce org, the setup flow is roughly: 1. Open **Setup**. 2. Find **External Client App Manager**. 3. Create a new External Client App. 4. Choose a local distribution state when the app is specific to this org. 5. Enable OAuth settings. 6. Add the exact callback URL used by the client. 7. Select the minimum required OAuth scopes. 8. Enable only the OAuth flows the client actually uses. 9. Create the app. 10. Configure its policies and permitted users. 11. Retrieve the consumer key—and secret only if the chosen flow requires one. The user interface separates settings and policies, so expect to visit more than one tab. Creating the ECA definition is not the same as finishing its access policy. Also expect propagation time. A newly created or changed OAuth client may not authenticate immediately. Waiting a few minutes is irritating but preferable to “fixing” five unrelated settings during propagation and losing track of which change mattered. > **Visual placeholder:** Screenshot walkthrough of External Client App Manager. Use numbered callouts for Basic Information, OAuth settings, callback URL, scopes, flow enablement, and Policies. Blur the consumer key and all org-specific values. ## Choose the flow before choosing the boxes Salesforce offers several OAuth flows because integrations have different identities. An interactive web dashboard commonly uses Authorization Code with PKCE so each employee signs in and Salesforce issues a token representing that user. A background service with no interactive user may use client credentials or JWT bearer, operating as a configured integration identity. That can be appropriate for controlled automation, but it creates a different audit and permission model. An MCP client connecting to Salesforce Hosted MCP uses an ECA with the callback and scopes required for that MCP client. Salesforce explicitly notes that Connected Apps cannot be used for Hosted MCP authentication. Do not enable every flow “for flexibility.” Each enabled flow is another supported path into the integration and another thing an auditor, administrator, or future maintainer must understand. ## Scope selection is not a wish list OAuth scopes are the outer boundary of what a token may request. Select the smallest set that supports the actual use case. A read-only external dashboard generally needs API access and identity-related scopes, plus refresh-token access if the application maintains a longer-lived server session. It does not need broad administrative scopes. A Salesforce Hosted MCP connection requires the MCP-specific scope documented for that service. Scopes are only one part of the authorization story. The signed-in user’s Salesforce permissions still matter in a user-delegated flow. That is a feature: the same dashboard can respect existing record and field access instead of inventing a parallel entitlement system. ## What about existing Connected Apps? Start with an inventory rather than a migration sprint. For each app, document: - Owner and business purpose - Active users or integration identity - OAuth flows and callbacks - Requested scopes - Permitted-user policy - Token and session policy - Last observed use - Production dependencies - Rotation and revocation process You will probably discover three categories: healthy integrations worth migrating carefully, unused apps worth retiring, and mystery apps nobody wants to touch. The third category is not a reason to ignore the inventory; it is the reason the inventory is valuable. When you migrate, use a test environment and plan for parallel validation. Confirm sign-in, token refresh, user access, API behavior, logout or revocation, monitoring, and rollback. Do not expose consumer secrets in screenshots, deployment logs, or work items. ## The opportunity hidden inside the change Platform transitions are annoying when they invalidate a memorized sequence of clicks. They are useful when they force us to revisit old assumptions. External Client Apps provide a good reason to ask: - Does this integration still need all of its scopes? - Is it acting as the right identity? - Can administrators tell who is allowed to use it? - Is its configuration in source control? - Can we revoke it without an archaeological expedition? The headline is that Connected App creation is restricted. The more useful story is that Salesforce integrations now have a cleaner forward model—one that separates the software definition from the organization’s decision to trust it. That is a change worth understanding before the next OAuth callback starts failing at 4:55 on a Friday. ## References - [Salesforce: The Developer’s Guide to the Spring ’26 Release](https://developer.salesforce.com/blogs/2026/01/developers-guide-to-the-spring-26-release) - [Salesforce: Secure Your Org with External Client Apps](https://developer.salesforce.com/blogs/2025/01/secure-your-org-with-external-client-apps) - [Salesforce: Create an External Client App](https://developer.salesforce.com/docs/platform/mobile-sdk/guide/eca-create.html) - [Salesforce: Set Up Your Org for Hosted MCP Servers](https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/setup-overview.html) --- # How to Build a Read-Only Salesforce Dashboard with an External Client App Canonical: https://neurobase.dev/articles/build-a-read-only-salesforce-dashboard-with-an-external-client-app Markdown: https://neurobase.dev/articles/build-a-read-only-salesforce-dashboard-with-an-external-client-app.md An implementation guide for a user-authenticated Salesforce dashboard using an External Client App, Authorization Code with PKCE, a backend-for-frontend, bounded SOQL queries, and no write operations. Salesforce contains an enormous amount of useful operational data. It does not follow that every useful way to view that data must live inside Salesforce. Maybe your team wants a focused pipeline screen, an underwriting workspace, a morning operating dashboard, or a small internal tool that combines Salesforce records with another system. An external dashboard can provide that experience without turning into a second CRM—if you preserve one crucial boundary: **Salesforce remains the system of record, and the dashboard only reads it.** This article walks through a user-authenticated architecture using a Salesforce External Client App, OAuth Authorization Code with PKCE, a backend-for-frontend, and read-only API operations. The result is an external dashboard that respects each user’s Salesforce permissions instead of sharing one all-powerful integration account. > **Visual placeholder:** Opening GIF. User signs in with Salesforce, lands on a polished pipeline dashboard, changes a filter, and clicks “Open in Salesforce” on one record. Use a developer org or fully synthetic data. ## The architecture The dashboard has four main pieces: 1. A browser frontend displays charts, tables, and filters. 2. Your backend owns the OAuth flow and calls Salesforce APIs. 3. A Salesforce External Client App defines the OAuth trust relationship. 4. Salesforce authorizes the signed-in user and returns only data that identity may access. The browser should not become a permanent token wallet. A practical design stores Salesforce tokens in a server-side session store and gives the browser only an opaque, secure session cookie. The browser asks your backend for `/api/dashboard`. The backend finds the user’s Salesforce token, runs bounded queries, shapes the results into dashboard-friendly JSON, and returns only the fields the UI needs. > **Visual placeholder:** Architecture diagram. Browser → application backend → Salesforce REST API. Draw the OAuth redirect through Salesforce login and show tokens stored only on the server. ## Why use the signed-in user’s identity? An external dashboard could authenticate to Salesforce using one shared service account. That is simple at first and expensive later. With a shared identity, every dashboard user potentially sees whatever the integration account can see. Your application must recreate record-level authorization, field visibility, business-unit boundaries, and offboarding logic. It is easy to accidentally build a second, worse version of Salesforce security. With per-user OAuth, Salesforce evaluates queries as the signed-in user. Existing profiles, permission sets, sharing rules, and record access continue to matter. When someone loses access in Salesforce, the dashboard naturally loses the same access. This does not eliminate application authorization. Your app may still restrict who can use the dashboard at all. It does avoid giving the app broader Salesforce visibility than the user it serves. ## Step 1: Create the External Client App In Salesforce Setup, open **External Client App Manager** and create a local External Client App for the dashboard. Enable OAuth and register the backend’s exact callback URL, for example: `https://dashboard.example.com/api/auth/salesforce/callback` For local development, add a separate HTTPS or loopback callback appropriate to your framework and Salesforce configuration. Keep development and production values explicit; do not rely on replacing random pieces of a URL at runtime. Select the minimum scopes required. A typical user-delegated API dashboard may need: - Identity information - API access - Refresh-token access if server sessions should survive access-token expiry Enable Authorization Code and require PKCE. Do not enable client credentials, username-password, or other flows merely because they are available. After creating the app, configure its policy. Decide whether users can self-authorize or must be pre-approved. For an internal enterprise dashboard, pre-authorization through appropriate profiles or permission sets is often easier to govern. > **Visual placeholder:** Salesforce setup screenshot sequence. Show callback URL, selected scopes, PKCE requirement, and permitted-user policy. Consumer credentials must be blurred. ## Step 2: Start the OAuth flow safely When a user clicks **Sign in with Salesforce**, the backend should create two random values: - A `state` value to bind the callback to the browser session and prevent login CSRF - A PKCE verifier, from which it derives the code challenge sent to Salesforce Store the state and verifier server-side or in a protected, short-lived mechanism tied to the login attempt. Then redirect the browser to the Salesforce authorization endpoint with the client ID, callback URL, requested scopes, state, and PKCE challenge. After the user signs in, Salesforce redirects to your callback with an authorization code and the original state. Validate the state before exchanging anything. Then send the code and PKCE verifier to the token endpoint. If successful, Salesforce returns an access token and related identity details. Store the token in a server-side session keyed by an opaque cookie. Set the cookie `Secure`, `HttpOnly`, and with an intentional `SameSite` policy. The frontend never needs to read the Salesforce access token. > **Visual placeholder:** Animated OAuth sequence. Highlight the random state traveling out and back; keep the PKCE verifier inside the backend until token exchange. ## Step 3: Define a read-only Salesforce boundary “This dashboard is read-only” should be a property of the implementation, not a sentence in a prompt or README. Start by exposing only query-oriented backend services. Do not build a generic Salesforce request proxy that accepts arbitrary methods and paths from the browser. A route like `/api/salesforce?path=...` eventually becomes an authorization incident with excellent flexibility. Instead, create functions with narrow intent: - `get_pipeline_summary(user_session, filters)` - `get_recent_opportunities(user_session, limit)` - `get_account_detail(user_session, account_id)` - `search_accounts(user_session, query, limit)` Keep SOQL in server-owned code. Parameterize or strictly validate filter values. Allowlist sortable fields and filter operators. Cap every result set and use pagination for detail tables. At the HTTP layer, expose only `GET` or semantically read-only `POST` endpoints when a complex filter body is necessary. Do not include Salesforce create, update, upsert, delete, or composite-write operations in the application client. For defense in depth, use Salesforce permissions that prevent the relevant users or integration context from writing the objects in scope where that matches the business requirement. Code restrictions are useful; platform permissions are harder to bypass accidentally. > **Visual placeholder:** “Read-only by construction” diagram showing four locks: no write routes, query-only service, SOQL allowlists, and Salesforce permissions. ## Step 4: Shape data for the dashboard Do not make the frontend understand raw Salesforce records if you can avoid it. Suppose the dashboard needs pipeline totals by stage, upcoming maturities, and a list of recently modified opportunities. The backend can run a few deliberate queries and return a view model such as: ```json { "asOf": "2026-07-22T14:30:00Z", "pipeline": { "totalAmount": 42500000, "dealCount": 18, "byStage": [ { "stage": "Underwriting", "count": 7, "amount": 18200000 } ] }, "recentDeals": [] } ``` This creates a stable contract between backend and frontend. If a Salesforce field is renamed, calculated differently, or replaced, one mapping layer changes instead of every component. Return an `asOf` timestamp and make freshness visible. Users trust dashboards more when the interface admits when the data was retrieved. ## Step 5: Avoid the N+1 query trap A dashboard often begins with one Opportunity query. Then each row needs an Account. Each Account needs an owner. Each deal needs its latest activity. Suddenly loading 25 rows triggers 76 API calls. Design the query shape before polishing the skeleton loader. Use Salesforce relationship queries and aggregate queries where appropriate. Batch related identifiers and fetch them in sets. Cache stable reference data briefly when permitted, but keep the user identity in the cache key whenever visibility can differ by user. Never share a cached response across users unless you have proven the underlying data and permissions are identical. “It was faster” is not an acceptable explanation for cross-user record leakage. > **Visual placeholder:** Before/after waterfall. Before shows dozens of Salesforce calls; after shows three bounded calls followed by one dashboard response. ## Step 6: Add “Open in Salesforce” before adding edits Users will eventually want to act on what they see. The safest first action is a deep link back to the corresponding Salesforce record. Add an **Open in Salesforce** link using the record ID and the org’s approved domain. This keeps updates in the system that already owns validation rules, approval flows, audit history, and permissions. That small feature often delivers most of the workflow value people actually need: discover and prioritize work in the dashboard, then perform the official update in Salesforce. If you later add write actions, treat them as a separate product and security phase. Define each allowed mutation, add confirmation and idempotency behavior, record an audit trail, and review field-level authorization. Do not let “just add a status dropdown” quietly dissolve the read-only boundary. ## Step 7: Handle token expiry and revocation honestly Access tokens expire. Refresh tokens can be revoked. Salesforce sessions can be restricted by policy. Your dashboard should treat those events as normal operating conditions. When an API call fails because authorization is no longer valid, clear the server session and return an explicit reauthentication response. The frontend should offer **Reconnect Salesforce**, not display “Something went wrong” forever while retrying a doomed token. Do not wrap every Salesforce error in the same generic exception. Distinguish at least: - Reauthentication required - User lacks access to a record or field - Invalid filter or query - Salesforce rate or availability problem - Internal application error Useful errors reduce support load and stop developers from weakening security simply to make a message disappear. ## Step 8: Verify the boundary Test with at least two Salesforce users who have meaningfully different access. Verify that: - Both can sign in through the ECA. - Each sees only records available to that Salesforce identity. - Restricted fields do not appear in API responses or logs. - A user without ECA approval cannot authorize. - State mismatch is rejected. - Expired or revoked tokens produce a reconnect path. - Search and filter inputs cannot alter the intended SOQL structure. - No frontend or backend route can create, modify, or delete Salesforce data. - Deep links open the correct record in the correct org. - Signing out clears the application session. Include negative tests in the demo. Showing that the dashboard refuses an unauthorized record is more persuasive than showing the same bar chart for the sixth time. > **Visual placeholder:** Verification montage: two users with different record counts, a blocked record, a clean reconnect screen, and a successful deep link to Salesforce. ## The result An external Salesforce dashboard does not need to become a shadow CRM. It can be a focused lens: authenticate each person, query within their existing permissions, shape a few useful views, and send them back to Salesforce for official changes. The External Client App establishes trust. OAuth and PKCE protect the sign-in flow. Server-side token storage keeps credentials out of browser code. Narrow query services preserve the read-only boundary. And the user’s own Salesforce identity prevents your app from pretending it knows more about authorization than Salesforce does. That architecture is not as flashy as a dashboard with fifty editable widgets. It is much easier to explain to a security reviewer—and, more importantly, much harder to regret. ## References - [Salesforce: Create an External Client App](https://developer.salesforce.com/docs/platform/mobile-sdk/guide/eca-create.html) - [Salesforce: OAuth and Connect REST API](https://developer.salesforce.com/docs/platform/connect-rest-api/guide/intro_using_oauth.html) - [Salesforce: Secure Your Org with External Client Apps](https://developer.salesforce.com/blogs/2025/01/secure-your-org-with-external-client-apps) Generated from the canonical Neurobase product metadata, documentation summaries, and published article sources.