<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kyle Mistele</title>
    <description>The latest articles on Forem by Kyle Mistele (@kylemistele).</description>
    <link>https://forem.com/kylemistele</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3271233%2Fded30851-fef1-48d1-8319-16c2dacff4ab.jpg</url>
      <title>Forem: Kyle Mistele</title>
      <link>https://forem.com/kylemistele</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kylemistele"/>
    <language>en</language>
    <item>
      <title>MCP Deep Dive: the Great, the Broken, and the Downright Dangerous</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Wed, 09 Jul 2025 15:13:27 +0000</pubDate>
      <link>https://forem.com/kylemistele/mcp-deep-dive-the-great-the-broken-and-the-downright-dangerous-31gl</link>
      <guid>https://forem.com/kylemistele/mcp-deep-dive-the-great-the-broken-and-the-downright-dangerous-31gl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;MCP's vision began with a great idea: a universal standard to connect AI models with arbitary tools and capabilities in a seamless, plug-and-play way. It's a fantastic vision, and as a result, a lot of people are &lt;a href="https://dev.to/blog/thoughts-about-mcp"&gt;very excited about MCP&lt;/a&gt;.&lt;br&gt;
The vision is evident in its specification, and the appeal of it is evident in the &lt;a href="https://cursor.directory" rel="noopener noreferrer"&gt;truly enormous number of MCP servers&lt;/a&gt; that people built nearly overnight once MCP was released, and in the great deal of writing about it.&lt;/p&gt;

&lt;p&gt;However, once we move past that lofty vision, we descend into the (presently) broken reality, where poor design decisions and broken implementations abound, and where some serious security problems lay hidden.&lt;/p&gt;

&lt;p&gt;As someone who has spent recent months deep in the trenches building with MCP, I'm familiar with what's great about MCP, what's broken, and what's downright dangerous. This is my report from the front lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good: A Visionary Specification with Untapped Potential
&lt;/h2&gt;

&lt;p&gt;On paper, &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;the MCP specification&lt;/a&gt; is impressive. It provides a rich, universal framework for connecting AI models to tools, data, and services. The most well-known primitive is tools, but the spec offers so much more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt;: For structured data access (e.g., connecting to a database).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;: For providing specific context to a model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roots&lt;/strong&gt;: For anchoring a model's operational context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sampling&lt;/strong&gt;: An awesome primitive that could enable advanced use-cases like bidirectional communication between a model and a tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt;: Integrating an MCP server with an upstream OAuth authorization server allows a user to authorize an agent to take actions on his or her behalf with 3rd-party apps and services. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spec also defines multiple different &lt;strong&gt;transports&lt;/strong&gt; that provide optionality in how to use and deploy MCP. These primitives offer a powerful, holistic way to build sophisticated AI-powered applications. &lt;/p&gt;

&lt;p&gt;And lots of great things have been built with MCP! I use Puppeteer's MCP server to enable faster testing and iteration of UIs in my Cursor agent, and Supabase/Postgres MCP servers to give my agent database access, and a number of other things.&lt;/p&gt;

&lt;p&gt;So much cool stuff has been built! But unfortunately, so much of the specification's potential remains largely untapped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broken: Where Implementations Fall Short, and the Spec Fails
&lt;/h2&gt;

&lt;p&gt;This is where the promise of MCP meets a harsh reality of incompleteness. Many parts of the MCP ecosystem are broken, either through incomplete implementation or because the specification itself is flawed.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MCP Implementers Only Support Tools
&lt;/h3&gt;

&lt;p&gt;Most MCP hosts like Cursor and Windsurf &lt;strong&gt;only implement the &lt;em&gt;tools&lt;/em&gt; primitive of MCP&lt;/strong&gt;, and choose not to support resources, roots, prompts, and/or sampling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Universality&lt;/strong&gt;. Tools are the most straightforward primitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opinionation &amp;amp; Compatibility&lt;/strong&gt;: A primary critique of MCP which it released was that it seemed to be opinionated in favor of Anthropic's models. Apart from the Claude family of models, most models don't have a good way to model any of the MCP primitives apart from tools. So most MCP hosts apart from Anthropic-specific ones like Claude desktop stick to just tools because they are portable to non-Anthropic models in a comparatively easy manner. Other primitives would require special handling or additinoal abstractions and complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workarounds&lt;/strong&gt;: Resources can often be crudely implemented as tools, so implementers don't bother with a proper implementation.&lt;br&gt;
This is a broken practice. Many things currently modeled as tools, like database connectors, should really be resources. By ignoring the other primitives, the ecosystem is limiting MCP's power to its lowest common denominator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and additional considerations&lt;/strong&gt;: Since &lt;strong&gt;sampling&lt;/strong&gt; allows the MCP server to prompt the agent it's connected to, there are &lt;br&gt;
security and trust considerations about if/how to allow this. Do you just let the LLM use the tools from the MCP server that's doing the sampling, or all tools that are plugged into the agent? The latter would enable much more powerful functionality, but also introduces worse risks for a rogue MCP server.(As I'll discuss later though, you should treat MCP servers like code — if you can't trust it 100%, you shouldn't run it).&lt;/p&gt;

&lt;p&gt;So ultimately the decision to primarily support the tools primitive is understandable, while regrettable — it's the only one &lt;br&gt;
that's truly portable and model-agnostic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Transport Layer Mess
&lt;/h3&gt;

&lt;p&gt;MCP's transport options are a mixed bad. Between inconsistent feature support, blurred lines, and unsuitability for production use-cases,&lt;br&gt;
it can be hard for implementers to choose the right transport the newer Streamable HTTP transport, which is optionally stateless, is really great! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STDIO works well for local apps like Cursor &amp;amp; Windsurf, but is not well-suited for production&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;To use an MCP server that uses the STDIO transport, you launch another process with &lt;code&gt;npx&lt;/code&gt;, &lt;code&gt;bunx&lt;/code&gt;, &lt;code&gt;uv&lt;/code&gt;/&lt;code&gt;uvx&lt;/code&gt; or something simliar, and then MCP JSON-RPC messages are sent into the process via STDIN, and received via STDOUT. This works well for applications running locally on your host, but there are a number of problems for deploying this as an application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hidden Dependencies&lt;/strong&gt;: MCP servers often rely on tools &amp;amp; toolchains like &lt;code&gt;npx&lt;/code&gt;, &lt;code&gt;uv&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, etc. to install and run the MCP server's source code and dependencies. These dependencies are not tracked in your project's version control, and may not be installed in your deployment environment (if they're installable at all, e.g. with Vercel's platform)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-Lived Stateful Connections&lt;/strong&gt;: STDIO servers require a long-lived stateful connection between the MCP client and the server. This doesn't work for serverless deployment platforms that use short-lived functions like AWS Lambda, Vercel, and certain cloudflare products. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor Scaling Properties&lt;/strong&gt;: For each agent loop/thread/instance that you have that needs to use the MCP server, you need &lt;em&gt;an entirely separate instance of the MCP server&lt;/em&gt; in a new process. This means that the number of processes you have to run scales linearly with the number of agents that you have. These processes can use up system resources, especially memory. In an idea world, you'd want one server instance to be able to serve
multiple different agent instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature incomplete-ness&lt;/strong&gt;: STDIO MCP servers are not compatible with the &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization" rel="noopener noreferrer"&gt;OAuth portion of the MCP Specification&lt;/a&gt;; to do so you must use an MCP bridge/proxy like &lt;a href="https://www.npmjs.com/package/mcp-remote" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-remote&lt;/code&gt;&lt;/a&gt;. This may not seem like a big problem right now, but lack of feature parity between transports makes transports less transparent to applications and makes the protocol more difficult to maintain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Similar problems obtain with SSE.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hidden Dependencies&lt;/strong&gt;: Similar to STDIO, this is a problem with SSE too. Especially for remote SSE servers. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-lived stateful connections&lt;/strong&gt;: Similar to STDIO, SSE requires long-lived stateful connections that are unsuitable for serverless environments. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor Scaling Properties&lt;/strong&gt;: SSE does not multiplex multiple clients to a single server, so you must have a unique SSE instance for each agent instance. This is additionally a problem because SSE binds to a port, so you must either use different ports and implement a discovery mechanism, or implement a proxy that handles session tracking and routing. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unclear best practices&lt;/strong&gt;: Unlike STDIO, SSE can be used for both local and remote MCP servers. It supports OAuth, but is more commonly used with API keys, session tokens, or bearer tokens. How to handle initiating multiple SSE servers and port binding with proxies, and authorization and routing are all open questions; deploying SSE to production for both production and local use-cases requires 
implementing solutions to this that are not part of the specification and for which best practices do not exist. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Streamable HTTP is the right way forward, but still has problems.&lt;/strong&gt;&lt;br&gt;
The streamable HTTP transport fixes a lot of the problems with STDIO and SSE. It's optionally stateless by specification, which means that &lt;br&gt;
individual MCP servers can elect to implement streaming, or just a shorter-lived request/response pattern. It's up to clients to roll with whatever the server chooses, though — there's no negotiation (which is fine imho, just worth nothing).&lt;/p&gt;

&lt;p&gt;The streamable HTTP transport &lt;em&gt;also&lt;/em&gt; supports OAuth, which is a big win. But there are still a few major drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Despite being adopted &lt;em&gt;months ago&lt;/em&gt;, most popular MCP hosts still don't support this transport. As a result, most servers don't support it. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlike the STDIO and SSE transports&lt;/strong&gt;, streamable HTTP servers do &lt;em&gt;not&lt;/em&gt; separate the transport from the server in the same way. 
The user must write a web app which properly consumes/implements the transport, and which calls the MCP server when appropriate. 
Reference implementations exist in the documentation, but this is a pain for people who want to write a server once and then deploy it with arbitrary transports. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. The OAuth Debacle
&lt;/h3&gt;

&lt;p&gt;MCP supports OAuth, yay! &lt;em&gt;Unfortunately&lt;/em&gt;, this is a place where &lt;strong&gt;both the specification and the implementation of the specification are bad&lt;/strong&gt;. Adding OAuth to MCP was &lt;em&gt;100%&lt;/em&gt; the right idea, but the execution was frankly poor. Why? &lt;/p&gt;

&lt;p&gt;The specification incorrectly mandates than an MCP server should behave &lt;strong&gt;both&lt;/strong&gt; as an OAuth &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1" rel="noopener noreferrer"&gt;authorization server&lt;/a&gt;, &lt;strong&gt;and&lt;/strong&gt; as an OAuth &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.1" rel="noopener noreferrer"&gt;resource server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://x.com/jaredhanson" rel="noopener noreferrer"&gt;Jared Hanson&lt;/a&gt;, the creator of Passport JS, founder of &lt;a href="https://keycard.ai" rel="noopener noreferrer"&gt;Keycard Labs&lt;/a&gt;, and author of most OAuth-Related JavaScript code on the internet &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/133#pullrequestreview-2541159319" rel="noopener noreferrer"&gt;observed in the MCP OAuth RFC&lt;/a&gt;, &lt;strong&gt;the proposed (and later accepted) RFC has an incorrect model and implementation of the protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is poor from a protocol design standpoint, this is poor from a security standpoint, this is bad from a compatibility standpoint, and this is bad from a &lt;strong&gt;server builder's standpoint&lt;/strong&gt;. The decision to model MCP this way seems to have been made out of convenience and ease-of-implementation concerns rather than longer-term concerns. &lt;/p&gt;

&lt;p&gt;For what it's worth, the MCP specification &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#standards-compliance" rel="noopener noreferrer"&gt;relies on several OAuth extensions&lt;/a&gt;&lt;br&gt;
including the rarely-used &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;RFC7591 Dynamic Client Registration Protocol&lt;/a&gt; to facilitate on-the-fly client registration with the MCP server since it behaves as an authorization server, &lt;br&gt;
as well as the (relatively) new &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728" rel="noopener noreferrer"&gt;RFC9728 Protected Resource Metadata&lt;/a&gt; standard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Okay, but why is this a problem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm so glad you asked. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;, OAuth-enabled MCP server implementers now have to either (a) implement all of these OAuth capabilities &lt;em&gt;into their MCP servers&lt;/em&gt;, or &lt;br&gt;
(b) have to _proxy all OAuth-related requests to an upstream OAuth authorization server. &lt;strong&gt;Both of these are exceptionally bad options.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MCP server developers should not have to implement OAuth and multiple rarely-used OAuth extensions to ship a streamable HTTP server that supports OAuth. &lt;strong&gt;To suggest otherwise is frankly ridiculous&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/modelcontextprotocol/typescript-sdk/tree/main?tab=readme-ov-file#proxy-authorization-requests-upstream" rel="noopener noreferrer"&gt;reference code&lt;/a&gt; is &lt;strong&gt;incomplete and broken&lt;/strong&gt;. When I implemented the &lt;a href="https://github.com/napthaai/http-oauth-mcp-server" rel="noopener noreferrer"&gt;second Streamable HTTP- and OAuth-compatible MCP server to exist&lt;/a&gt;, I had to &lt;a href="https://github.com/NapthaAI/http-oauth-mcp-server/blob/main/src/lib/extended-oauth-proxy-provider.ts" rel="noopener noreferrer"&gt;subclass and re-implement multiple parts of OAuth-related functionality&lt;/a&gt; due to broken dynamic client registration, hard-coded URLs, 
and proxy logic that &lt;strong&gt;will not work for 99% of OAuth authorization server providers&lt;/strong&gt; due to lack of support for the &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client registration&lt;/a&gt; OAuth extension which &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization" rel="noopener noreferrer"&gt;MCP relies on&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The approach recommended by the docs and reference code, of proxying to an upstream OAuth provider, has several security concerns that are not described in the docs, and which implementers would not know to provide for. I will write more on this below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, A core principle of OAuth is the separation of concerns. This design is not just wrong; it's a known security anti-pattern. &lt;/p&gt;

&lt;p&gt;Furthermore, the specification's reliance on &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client Registration&lt;/a&gt; was a terrible decision. Almost no major OAuth providers (Google, GitHub, etc.) support it out of the box. Auth0 supports it, but requires you enable it since it's not enabled by default. The ability to do this is buried deep in settings menus, and your dashboard and tenant get incredibly messy (and messed-up) if you enable it since Auth0 does not intend for this use-case. &lt;/p&gt;

&lt;p&gt;This means that to build a compliant MCP server, &lt;strong&gt;even if you implement a proxy to an upstream OAuth provider&lt;/strong&gt;, in most cases you will &lt;em&gt;still&lt;/em&gt; have to implement some type of wrapper to support Dynamic Client Registration and other parts of OAuth yourself just to proxy to an upstream provider. &lt;/p&gt;

&lt;p&gt;As I mentioned above, this isn't theoretical — I had to rewrite broken OAuth code in the official TypeScript MCP SDK to get my server working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dangerous: A Pragmatic Guide to MCP Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Remote Code Exection on your Host
&lt;/h3&gt;

&lt;p&gt;With all the talk about MCP security (some of which is motivated by opportunistic security vendors), you might think that MCP requires a complex new threat or risk model. It doesn't, at least for local servers. &lt;strong&gt;The trust model is simple: MCP should be treated exactly like any other third party code dependency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's how you should think about it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local MCP Servers (STDIO &amp;amp; SSE)&lt;/strong&gt;: Treat any MCP server you install and run locally the same way that you would an &lt;code&gt;npm&lt;/code&gt; or PyPi package. They are &lt;em&gt;arbitrary code&lt;/em&gt;. If you don't trust the author and the distribution source, &lt;em&gt;don't download and run it&lt;/em&gt;. It's that simple. 
A malicious local MCP server could execute arbitrary code, or prompt-inject your LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote MCP Servers (SSE, Streamable HTTP)&lt;/strong&gt;: Treat any remote MCP server just like any untrusted software. If you don't trust the author and/or domain, &lt;em&gt;do not connect to the server or send it credentials&lt;/em&gt;. In a sense, you should actually also treat it as local MCP server in that you should treat it as untrusted code - since in addition to stealing credentials, a rogue remote MCP server could easily trick your coding agent into writing and executing malicious code. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the specific implementations of the attack (arbitary code execution by malicious software) differ slightly for remote MCP servers in that they are remote apps that can cause code execution on your local device, the model you should have for both is simple. &lt;strong&gt;Do not blindly trust code that you didn't write&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth-related security flaws
&lt;/h3&gt;

&lt;p&gt;Having said that, there are other issues that can (and commonly do) arise in MCP servers' implementation of the OAuth part of the MCP specification for remote MCP servers that you need to be aware of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access token issuance when using MCPs as proxies for upstream OAuth Authorization servers&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As noted, if you are proxying an MCP server's authorization logic to an upstream Authorization server, you may be tempted to return the access token from the upstream authorization server to the downstream client. &lt;strong&gt;You should not do this&lt;/strong&gt;; rather you should verify the access token from the server, and then issue a sort of "proxy token" to the client, which the client can then to use with your MCP server, and upon verification when necessary, your MCP server can make authorized requests to resource servers with the actual access token from the authorization server. &lt;/p&gt;

&lt;p&gt;Exposing the access token from the upstream authorization server to the downstream client can allow a compromised or malicious client &lt;br&gt;
to access resources with upstream resource servers in undesired ways, especially if the issued access token has over-permissive scopes. Issuing a sort of "proxy access token" to the client, and ensuring that the only time that the real access token is used is in requests from your MCP server to the correct resource servers, prevents this attack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phishing attacks by malicious MCP servers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Regrettably, I can't share a lot about this attack right now since it's a severe issue in the current protocol revision (&lt;code&gt;2025-06-18&lt;/code&gt;) that is (a) unpatched, and (b) trivially exploitable. You should think of this is a High- or Critical-severity CVE issue involving credential theft.&lt;/p&gt;

&lt;p&gt;I was initially going to write about this in more detail, but all information about it has been stripped from the public GitHub threads &amp;amp; discussions, and numerous tweets about it have been deleted. I didn't get the memo directly, but this indicates that the protocol maintainers are not ready for the specifics of this issue to &lt;br&gt;
be made public until it can be remediated, so I have elected to not share specifics at this time. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: A Plea for Refinement
&lt;/h2&gt;

&lt;p&gt;My journey with MCP reveals a protocol that has a great vision, but a flawed execution. To move forward, the community needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clean up the transport mess&lt;/strong&gt;: Deprecate the SSE transport in favor of streamable HTTP transport; support OAuth in the STDIO transport, and provide 
clear recommendations about when it is (and is not) appropriate to use each transport. Also, better-separate concerns in the streamable HTTP transport's implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the OAuth extension to the spec&lt;/strong&gt;: The OAuth implementation must be revised. MCP servers should be modeled as resource servers &lt;em&gt;only&lt;/em&gt;, dropping dynamic client registration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve the ecosystem&lt;/strong&gt;: Broad client support for the streamable HTTP transport and for the OAuth extension are both needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embrace the full spec&lt;/strong&gt;: We need to move beyond a "tools only" world and enourage the use of all the powerful primitives that MCP has to offer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MCP has the potential to be a foundational layer for the next generation of AI applications. but to get there, we need to close the gap between its ambitious promise and its currently broken reality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix: Other MCP R&amp;amp;D
&lt;/h3&gt;

&lt;p&gt;Check out some of my other work on MCP with &lt;a href="https://naptha.ai" rel="noopener noreferrer"&gt;Naptha AI&lt;/a&gt; here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the first &lt;a href="https://github.com/NapthaAI/http-oauth-mcp-server" rel="noopener noreferrer"&gt;non-platform-opinionated MCP server that implemented OAuth &lt;em&gt;and&lt;/em&gt; the Streamable HTTP Transport&lt;/a&gt; shortly after is was approved; which required rewriting parts of the TypeScript SDK to fix broken OAuth code&lt;/li&gt;
&lt;li&gt;a fork of &lt;a href="https://github.com/K-Mistele/mcp-remote" rel="noopener noreferrer"&gt;Cloudflare's MCP-Remote&lt;/a&gt; which supported bridging the Streamable HTTP Transport (not just SSE) to STDIO transports before Cloudflare's did&lt;/li&gt;
&lt;li&gt;an early &lt;a href="https://github.com/NapthaAI/mcpaas" rel="noopener noreferrer"&gt;platform for securely deploying SSE MCP servers&lt;/a&gt; on the public internet via session keys, before OAuth was added to the specification&lt;/li&gt;
&lt;li&gt;a library &lt;a href="https://github.com/napthaai/automcp" rel="noopener noreferrer"&gt;for wrapping popular agent frameworks with MCP tool bindings&lt;/a&gt; so that agents can call other agents as MCP servers, before Google released A2A&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>oauth</category>
    </item>
    <item>
      <title>Why Everybody's so Excited about MCP</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Mon, 07 Jul 2025 16:49:31 +0000</pubDate>
      <link>https://forem.com/kylemistele/why-everybodys-so-excited-about-mcp-1l7d</link>
      <guid>https://forem.com/kylemistele/why-everybodys-so-excited-about-mcp-1l7d</guid>
      <description>&lt;h2&gt;
  
  
  So what even &lt;em&gt;is&lt;/em&gt; MCP?
&lt;/h2&gt;

&lt;p&gt;MCP, short for Model Context Protocol, is a protocol designed to enable developers and applications to provide context to agents &amp;amp; large language models (LLMs).&lt;br&gt;
The &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;MCP Specification&lt;/a&gt; includes support for a number of different "context" primitives that can be provided to an agent, including prompts, tools, and resources,&lt;br&gt;
as well as a primitive called "sampling" that isn't used much.&lt;/p&gt;

&lt;p&gt;Read that, understand that, and then &lt;em&gt;forget&lt;/em&gt; that. &lt;/p&gt;

&lt;p&gt;The primary use-case for MCP, and the only one that popular MCP clients like Cursor, Windsurf, and Claude code support, is tools. &lt;/p&gt;
&lt;h2&gt;
  
  
  MCP is about tools
&lt;/h2&gt;

&lt;p&gt;The primary use-case for MCP right now is to plug tools (and therefore new capabilities) into your agents in a low-code, low-configuration way.&lt;/p&gt;

&lt;p&gt;Here's an example of my favorite &lt;strong&gt;high-impact use-case&lt;/strong&gt; for software development: &lt;strong&gt;Puppeteer's MCP server&lt;/strong&gt;. By adding a simple JSON configuration to Cursor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"puppeteer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-puppeteer"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;my cursor agent can now drive puppeteer using 7 tools (including tools for navigation, interaction, screenshotting, and &lt;code&gt;eval()&lt;/code&gt;-ing JavaScript code). &lt;/p&gt;

&lt;p&gt;This unlocks an entirely new development workflow where I can have my agent write UI code, and then use puppeteer to interact with the UI directly. &lt;br&gt;
My agent can &lt;strong&gt;iterate on features faster&lt;/strong&gt;, design and implement UIs better*&lt;em&gt;,&lt;br&gt;
(since the agent can see the actual UI that the code creates, not just the code), &lt;br&gt;
**debug my app significantly faster&lt;/em&gt;*, and a lot more. &lt;/p&gt;

&lt;p&gt;Notably, without MCP, &lt;em&gt;this would be far more difficult&lt;/em&gt;. &lt;br&gt;
The best alternative would be for me to write a bunch of bash or JavaScript or Python scripts to implement these capabilities,&lt;br&gt;
and then update the agent's prompt to inform it of these scripts and how to use them, and then hope for the best. &lt;/p&gt;
&lt;h3&gt;
  
  
  Other examples
&lt;/h3&gt;

&lt;p&gt;Plugging Puppeteer into Cursor or Windsurf is just one example of a broad and diverse set of use-cases. Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugging Claude Desktop or ChatGPT into your Google Docs so that they can create, read and save documents&lt;/li&gt;
&lt;li&gt;Enabling agents to manage your Next.js deployments with the Vercel MCP server&lt;/li&gt;
&lt;li&gt;Enabling background agents to send you slack messages when they need assistance with the Slack MCP Server&lt;/li&gt;
&lt;li&gt;Enabling coding agents to update your GitHub/Linear/Jira issues &amp;amp; tickets as they make progress on issues and to make PRs when they're finished&lt;/li&gt;
&lt;li&gt;Enabling voice agents to query your knowledge base without custom code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all these cases, MCP allows you to plug in arbitrary capabilities to LLMs without having to write custom code to handle the connections, requests, responses, and abstractions.&lt;/p&gt;

&lt;p&gt;MCP servers function like no-code pre-built integrations for your agent that give it tools or allow it to access third-party apps and software.&lt;/p&gt;
&lt;h2&gt;
  
  
  How should you understand MCP?
&lt;/h2&gt;

&lt;p&gt;Given this, how should we understand MCP? Think about it this way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is a compatibility layer for extending agents &amp;amp; LLMs with arbitrary capabilities in a no-code manner&lt;/strong&gt;. You can think of it as a special type of API optimized for LLMs.&lt;/p&gt;

&lt;p&gt;Now, this isn't entirely accurate as it doesn't cover &lt;em&gt;everything&lt;/em&gt; that the MCP specification defines (including sampling), but this is a great mental model for the ways that MCP is actually, presently being used.&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP vs. Traditional APIs
&lt;/h2&gt;

&lt;p&gt;Let's dig more into this model of MCP servers are LLM-optimized APIs. Consider traditional APIs. They have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a transport (HTTP)&lt;/li&gt;
&lt;li&gt;an interface (usually JSON-based) that provides an abstract model for the underlying resources &amp;amp; capabilities&lt;/li&gt;
&lt;li&gt;a set of resources and capabilities that are exposed through that interface&lt;/li&gt;
&lt;li&gt;(usually) documentation for humans to read, e.g. a docs site or OpenAPI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;APIs then have to be implemented using a HTTP client in deterministic code, which has to be aware of the interface structure, the transport (your code has to set headers, query params, etc.), and all the other details except the actual implemnetation of the capabilities on the API's backend.&lt;/p&gt;

&lt;p&gt;MCP optimizes a lot of this for direct consumption by LLMs. Like a traditional API, MCP has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a transport (which may be STDIO, server-sent events or SSE, a streamable HTTP transport, or WebSockets though it is unofficial)&lt;/li&gt;
&lt;li&gt;an interface (JSON-RPC),&lt;/li&gt;
&lt;li&gt;and a set of resources and capabilities that they expose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But while traditional APIs are designed to be consumed by software implemented by developers, MCP is intended to be consumed directly by LLMs &amp;amp; agents, &lt;br&gt;
so there are some significant differences.&lt;/p&gt;

&lt;p&gt;The agent isn't aware of the specifics of the transport (no headers or query parameters), and it really isn't aware that the transport exists at all — &lt;br&gt;
it just sends JSON and receives JSON. There are far fewer implementation details to worry about.&lt;/p&gt;

&lt;p&gt;The agent may not even be aware it's using MCP — the tools may be presented to it just like normal tools, with names, descriptions, and input schemas. &lt;br&gt;
In this regard, MCP can be thought of as a "self-documenting API". There are pre-determined MCP "API" methods &lt;br&gt;
which list information about the server's resources and about the server's tools (including the name, description, and JSON schema describing the input).&lt;br&gt;
The LLM or agent can use these methods on a connected MCP server to discover the tools and resources that are available to it, and can then intelligently decide&lt;br&gt;
when and how to access or invoke them. &lt;/p&gt;
&lt;h2&gt;
  
  
  Note on Authorization
&lt;/h2&gt;

&lt;p&gt;Traditional APIs can handle auth however they want - JWTs, HTTP basic auth, API keys, OAuth, or whatever else they want.&lt;/p&gt;

&lt;p&gt;MCP now handles authentication &amp;amp; authorization capabilities through &lt;a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;, which allows &lt;br&gt;
users to authorize them to access their applications and data with third-party providers that use OAuth like Google, HubSpot, Linear, etc.&lt;/p&gt;

&lt;p&gt;When a user initializes an MCP server for an agent, they may be prompted to authorize access to whatever capabilities and tools the server offers with OAuth, so that they can be exposed to the agent without any type of API key or token.&lt;br&gt;
In practice, this means that when you plug in an MCP server to your client, you will be prompted through a GUI to grant access to the server &amp;amp; agent. &lt;/p&gt;

&lt;p&gt;Examples of where you might see this include in a Github or Google MCP server — servers where you are giving the LLM tools to manage remote resources in a third-party service. &lt;/p&gt;

&lt;p&gt;This frees you from the complexity of managing API keys, environment variables, and all that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why are people excited about MCP?
&lt;/h2&gt;

&lt;p&gt;If you're like me, you've seen dozens of Twitter and LinkedIn posts about how MCP is changing the world.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Well, is it? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No. But it's still pretty great. It has a lot to offer:&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP for ChatGPT / Claude Desktop users
&lt;/h2&gt;

&lt;p&gt;Non-technical users of ChatGPT, Claude Desktop and other MCP-compatible apps can use MCP to connect their LLM to their local filesystem, to Google Drive/Docs/Sheets/Slides, &lt;br&gt;
Brave search, Obsidian, Notion, the command line, data science tools and Jupyter notebooks, even Google Ads, and more. &lt;/p&gt;

&lt;p&gt;It enables them to connect their relatively limited LLM chat, which normally only has acess to web search and maybe code execution, to their favorite apps and tools, unlocking a whole new set of use-cases centered on productivity.&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP for "Vibe Coders"
&lt;/h2&gt;

&lt;p&gt;MCP is even more popular with "Vibe Coders", who are typically non-technical or low-technical individuals using AI-powered code generation tools like Cursor, WindSurf, Bolt, v0, or Lovable to build apps. &lt;/p&gt;

&lt;p&gt;MCP is popular with vibe coders becasueit enables them to upgrade their coding agent's capabilities to be more useful. For example, it could use the Vercel MCP server &lt;br&gt;
to handle setting up their Next.js app for deployment on Vercel, and running deployments and checking logs. &lt;/p&gt;

&lt;p&gt;Similarly, they could use a Supabase MCP server to check on their Supabase project and query the database.&lt;/p&gt;
&lt;h2&gt;
  
  
  MCP for Engineers
&lt;/h2&gt;

&lt;p&gt;MCP is popular with engineers because it allows them to level up their AI-powered coding tools like Cursor, Windsurf, Claude code, and even custom agents or agentic workflows with new capabilities, with relatively little configuration.&lt;/p&gt;

&lt;p&gt;I already gave the example of Puppeteer, and there are several more below. For engineers, MCP is a way to make coding agents smarter, more efficient, and more aware of important project context. &lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus: Cool MCP Servers you should try
&lt;/h2&gt;

&lt;p&gt;As part of doing research for this post, I dug into what the most popular MCP servers are. I found that a lot of them are behind paywalls on Medium, or are&lt;br&gt;
spread out across various registries. In this section, I've put together a list of some of my favorites that are worth checking out. &lt;/p&gt;
&lt;h2&gt;
  
  
  Context7 — Instant Context &amp;amp; Docs for your Stack
&lt;/h2&gt;

&lt;p&gt;Have you ever used Cursor's Documentation Indexing feature? Imagine having pre-index docs for all your favorite apps and services. &lt;br&gt;
Instead of having to paste URLs, index docs and then &lt;code&gt;@mention&lt;/code&gt; them in your agent chat, the Context7 MCP servers allows you to just &lt;br&gt;
ask your agent a question, and tell it&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;use context7&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to answer and it will automatically locate and query the up-to-date docs for whatever you need.&lt;/p&gt;

&lt;p&gt;You can find the Context7 MCP server &lt;a href="https://github.com/upstash/context7" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and you can install it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.context7.com/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Google Ads - Analyze your Google Ads Data with AI
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/cohnen/mcp-google-ads" rel="noopener noreferrer"&gt;Google Ads MCP Server&lt;/a&gt; is an &lt;em&gt;unofficial&lt;/em&gt; MCP server that wraps the google ads APIs and allows you to analyze your &lt;br&gt;
google ads data through natural language. You can install it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"googleAdsServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/FULL/PATH/TO/mcp-google-ads-main/.venv/bin/python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/FULL/PATH/TO/mcp-google-ads-main/google_ads_server.py"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_ADS_AUTH_TYPE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oauth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_ADS_CREDENTIALS_PATH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/FULL/PATH/TO/mcp-google-ads-main/credentials.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_ADS_DEVELOPER_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_DEVELOPER_TOKEN_HERE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_ADS_LOGIN_CUSTOMER_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MANAGER_ACCOUNT_ID_HERE"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this server requires some more technical information to successfully configure. &lt;/p&gt;

&lt;h2&gt;
  
  
  Puppeteer: Let Your Agent Drive a Web Browser
&lt;/h2&gt;

&lt;p&gt;My personal favorite server allows my Cursor agent to drive &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;, a browser automation framework. &lt;br&gt;
I love this server because it enables the agnet to navigate my apps as I'm building them, to interact with &lt;br&gt;
them, and to take screenshots. &lt;/p&gt;

&lt;p&gt;This rapidly speeds up the development cycle because the agent can see features and interfaces as it's building them, &lt;br&gt;
and it can interact with them to test, validate and debug them.&lt;/p&gt;

&lt;p&gt;You can install the server &lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/server-puppeteer" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"puppeteer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-puppeteer"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"PUPPETEER_LAUNCH_OPTIONS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{ &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;headless&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"ALLOW_DANGEROUS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if you want the agent to be able to drive &lt;em&gt;your&lt;/em&gt; browser with all of your cookies and sessions (which is useful for apps with auth), &lt;br&gt;
you can set another environment variable with the path to your chrome data directory. This path is different depending on your OS, but for my Macbook it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"CHROME_USER_DATA_DIR": "/Users/kyle/Library/Application Support/Google/Chrome/Default"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker: Let Your Agent Manage Your Containers
&lt;/h2&gt;

&lt;p&gt;This server is really useful for docker- and Docker compose-based projects. Instead of making your agent write shell commands, &lt;br&gt;
it gives your agent an easier way to manage docker and compose. I use this one a lot. You can install it &lt;a href="https://github.com/ckreiling/mcp-server-docker" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"--from"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"git+https://github.com/ckreiling/mcp-server-docker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"mcp-server-docker"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Postgres: Enabling your Agent to Safely Query your Database
&lt;/h2&gt;

&lt;p&gt;I work with postgres a lot whether through Supabase, Docker, AWS, or other servers. &lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/server-postgres" rel="noopener noreferrer"&gt;This one&lt;/a&gt; is really useful for me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bunx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres:YOUR_PASSWORD_HERE@pgvector:5432/postgres"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note that you need to make sure to not commit this to GitHub, unless you use another way of configuring your credentials!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe: Connect your Agent to your Stripe Account
&lt;/h2&gt;

&lt;p&gt;If you're building a SaaS app and are using Stripe for billing, &lt;a href="https://github.com/stripe/agent-toolkit/tree/main/modelcontextprotocol" rel="noopener noreferrer"&gt;this&lt;/a&gt; is a great one to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stripe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"@stripe/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"--tools=all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"--api-key=STRIPE_SECRET_KEY"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like above, make sure to not commit this to Git!&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase — Connect your Agent to your Supabase Project
&lt;/h2&gt;

&lt;p&gt;Lots of developers love Supabase's open-source platform for everything from postgres hosting, to object storage, to pub-sub and even auth. &lt;br&gt;
If you use Supabase for your projects, using &lt;a href="https://supabase.com/docs/guides/getting-started/mcp" rel="noopener noreferrer"&gt;their MCP server&lt;/a&gt; is a no-brainer!&lt;/p&gt;

&lt;p&gt;This server enables you to query your database and project in natural language. Super useful!&lt;/p&gt;

&lt;p&gt;Here's how you can install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"supabase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"@supabase/mcp-server-supabase@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--read-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--project-ref=&amp;lt;project-ref&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SUPABASE_ACCESS_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;personal-access-token&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Zapier — Connect your Agent to Thousands of Apps
&lt;/h2&gt;

&lt;p&gt;If you're a Zapier user, &lt;a href="https://zapier.com/mcp" rel="noopener noreferrer"&gt;this server&lt;/a&gt; is for you! &lt;br&gt;
Zapier's MCP server empowers your agent to use the apps and workflows you have connected in your Zapier account!&lt;/p&gt;

&lt;p&gt;This is a great way to integrate your agent with any of thousands of applications with a single MCP server, with no code! &lt;br&gt;
Just configure apps in your Zapier account, plug in your Zapier MCP server, and you're good to go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Ultimately, the best way to understand the excitement around MCP is to think of it as a specialized plug-and-play API for adding capabilities to LLMs. &lt;br&gt;
This simple mental model captures its core value: it allows anyone, from non-technical professionals to seasoned engineers, to easily extend their AI agents with &lt;br&gt;
powerful new tools and capabilities.&lt;/p&gt;

&lt;p&gt;By providing a no-code integration and compatibility layer, MCP makes agents more capable and useful for everyone today. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Footnote: I do &lt;strong&gt;not&lt;/strong&gt; recommend using MCP for production agents and agentic workflows. I'll cover this in another post&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>agents</category>
    </item>
    <item>
      <title>The OAuth Integration Nightmare: 8 Critical Components you'll need to build</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Mon, 23 Jun 2025 15:07:43 +0000</pubDate>
      <link>https://forem.com/kylemistele/the-oauth-integration-nightmare-8-critical-components-youll-need-to-build-i51</link>
      <guid>https://forem.com/kylemistele/the-oauth-integration-nightmare-8-critical-components-youll-need-to-build-i51</guid>
      <description>&lt;h2&gt;
  
  
  The OAuth Challenge
&lt;/h2&gt;

&lt;p&gt;I recently spent several weeks implementing OAuth flows for an &lt;a href="https://onedollaroauth.com" rel="noopener noreferrer"&gt;OAuth-related project&lt;/a&gt;, and I was struck by how much complexity is hidden beneath the surface. What looks like a simple authorization flow in theory requires building and maintaining at least eight distinct components - and many of these components differ for each OAuth provider you want to support.&lt;/p&gt;

&lt;p&gt;In this article, I'm going to walk you through the surprisingly complex world of OAuth integrations. If you've ever tried to implement an OAuth flow from scratch, you know it's not as simple as the diagrams make it seem. There are numerous moving parts, security considerations, and provider-specific quirks that can make this seemingly straightforward task a genuine headache.&lt;/p&gt;

&lt;p&gt;Worth clarifying: in this article, I'm not concerned with using OAuth (or more specifically &lt;a href="https://openid.net/developers/how-connect-works/" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt;, also known as OIDC) for user &lt;em&gt;authentication&lt;/em&gt; – that can be easily handled with solutions like &lt;a href="https://better-auth.com" rel="noopener noreferrer"&gt;Better Auth&lt;/a&gt; and similar. What I'm concerned with is actually building &lt;em&gt;integrations&lt;/em&gt; with OAuth apps so that you can use their APIs to do things, like sending messages in Slack or retrieving documents from Google Drive.&lt;br&gt;
There are a different set of considerations that these types of integrations imply.&lt;/p&gt;

&lt;p&gt;Also, for the purposes of this article, we're primarily concerned with the OAuth &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1" rel="noopener noreferrer"&gt;Authorization Code Flow&lt;/a&gt;. There are other OAuth flows like the &lt;a href="https://oauth.net/2/grant-types/implicit/" rel="noopener noreferrer"&gt;Implicit Flow&lt;/a&gt;, but this is the one that you will most-commonly use as it is more secure than the implicit flow.&lt;/p&gt;

&lt;p&gt;Let's break down these components one by one, and I'll share some code examples and lessons learned along the way.&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview the Authorization Code Flow
&lt;/h2&gt;

&lt;p&gt;Before we get into the components you'll need to build, let's do a quick overview of the Authorization code flow. Generally, there are five entities that are party to an Authorization code flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user &lt;/li&gt;
&lt;li&gt;The user's browser&lt;/li&gt;
&lt;li&gt;Your application&lt;/li&gt;
&lt;li&gt;The OAuth Authorization server that handles granting tokens&lt;/li&gt;
&lt;li&gt;The Resource server which the authorization server's tokens grant access to&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, don't worry if you don't know what all the terms here are - we'll get to them below! Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access (User) –&lt;/strong&gt; Ther user clicks a button in your app that's in their browser to initiate the OAuth flow to connect an app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Authorization (Your application) –&lt;/strong&gt; Your app generates a URL to the Authorization server's &lt;strong&gt;authorization endpoint&lt;/strong&gt; which includes a bunch of information including your client ID, a redirect URL, the &lt;code&gt;response_type&lt;/code&gt; set to &lt;code&gt;code&lt;/code&gt; to indicate we're using the Authoriztaion Code flow, and the requested scopes – along with other optional fields like &lt;code&gt;state&lt;/code&gt;, or provider-specific fields. The user is redirected to this URL at the authorization server in their browser, which requests authorization from the authorization server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display Consent (Authorization Server) –&lt;/strong&gt; The authorization server (e.g. Google's OAuth server) will ask the user if they want to authorize your app (identified by its client ID) to access whatever permissions and resources are specified by the scopes. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give Consent (User) –&lt;/strong&gt; The user can give consent for (&lt;em&gt;authorize&lt;/em&gt;) your app to access the requested resources identified by the scopes by clicking a button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue Authorization Code (Authorization server) –&lt;/strong&gt; The Authorization server will redirect the user's browser to your applications's &lt;strong&gt;redirect URL&lt;/strong&gt; with &lt;em&gt;either&lt;/em&gt; query parameters including a &lt;code&gt;code&lt;/code&gt; which is the Authorization code and the &lt;code&gt;state&lt;/code&gt; specified in step (2), or an &lt;code&gt;error&lt;/code&gt; indicating that an error occured. Note that the authorization code is an opaque token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Access Token (your application) –&lt;/strong&gt; Once your application receives the authorization code (and the &lt;code&gt;state&lt;/code&gt;), it should make a POST request from the &lt;strong&gt;backend&lt;/strong&gt; to the Authorization server's &lt;strong&gt;token endpoint&lt;/strong&gt; with your application's client ID and client secret, the authorization code from the previous step, the original redirect URL, and optionally other provider-specific fields. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue Access Token (Authorization server) –&lt;/strong&gt; If everythign is in order your application will receive a response containing &lt;em&gt;at minimum&lt;/em&gt; an &lt;code&gt;access_token&lt;/code&gt; and a &lt;code&gt;token_type&lt;/code&gt;. It may also receive an &lt;code&gt;expires_in&lt;/code&gt; field (in &lt;em&gt;seconds&lt;/em&gt;, not milliseconds) denoting when the access token expires, and a &lt;code&gt;refresh_token&lt;/code&gt; which does not expire, which may be used to obtain a new, unexpired access token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The access token can then be included in requests from your application to a resource server which trusts the authorization server (e.g. how the Google docs API trusts the Google OAuth authorization server) to take actions which are authorized by the scopes which were granted after being requested.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 8 Critical Components of OAuth Integrations
&lt;/h2&gt;

&lt;p&gt;With this understanding in mind, let's break down what you actually have to build in order to ship an OAuth integration.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Client Credentials Management
&lt;/h3&gt;

&lt;p&gt;Every OAuth integration requires a client ID and client secret. There's simply no way around this unless you're using someone else's credentials (which, as I'll explain, is a terrible idea).&lt;br&gt;
The Client ID and Client Secret are collectively referred to as your "Client Credentials".&lt;/p&gt;

&lt;p&gt;I've seen some developers use integration platforms like Pipedream or Composio that let you use their OAuth credentials. This is a recipe for disaster. If their credentials get compromised or restricted (perhaps because another user on their platform is misbehaving), your application will suddenly stop working. Always get your own client credentials.&lt;/p&gt;

&lt;p&gt;Usually, you can do this from your OAuth Provider's dashboard, for example under "OAuth Applications" which is under "Developer Settings" under your profile settings in Github, or under the OAuth Apps section of the Google Cloud dashboard.&lt;/p&gt;

&lt;p&gt;Some providers will require additional configuration once you obtain your app and create your client credentials. For example, Slack makes you configure at least one scope.&lt;/p&gt;

&lt;p&gt;Once you have these, you'll need to pass them to your application. Using a dedicated secrets manager is the best option, and using environment variables is next-best. But &lt;strong&gt;never, ever hard-code these values in your source code or track them in version control&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;abcdef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xyz123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// BETTER:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;

&lt;span class="c1"&gt;// BEST:&lt;/span&gt;
&lt;span class="c1"&gt;// (prevent downstream errors from missing values):&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requireEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;variableName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Environment Variable &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;variableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not in the process environment!`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// capture this in logging and telemetry.&lt;/span&gt;
  &lt;span class="k"&gt;throw &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;// we treat this as an un-recoverable error. &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Building a Callback URL Handler
&lt;/h3&gt;

&lt;p&gt;When you redirect users to the provider's authorization URL, they'll eventually be redirected back to your application with an authorization code. You need to build an endpoint to handle this callback.&lt;/p&gt;

&lt;p&gt;Note that you should probably start by defining the route in your app but stubbing out the contents so that you can configure the URL in the OAuth provider's dashboard as a valid redirect URL for your application, since most providers require you to provide a whitelist of these.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OAuthCallbackQueryParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;OAuthTokenResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/oauth/callback/google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextFunction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;OAuthCallbackQueryParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Verify state to prevent CSRF (more on this later) and load other information from the state.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;verifyOAuthState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid state parameter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Exchange the code for tokens (next step)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OAuthTokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exchangeCodeForTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Store tokens securely&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;storeTokensForUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OAuth callback error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OAuth callback failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that aside from the &lt;code&gt;error&lt;/code&gt; or &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;code&lt;/code&gt; parameters, some OAuth providers will include additional parameters. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Initiating the OAuth Flow
&lt;/h3&gt;

&lt;p&gt;Once you have a callback / redirect URL to receive the Authorization Code from the authorization server, you can initiate the OAuth flow for a user by redirecting the user to the Authorization Server's authorization URL with your client ID, redirect URL, list of requested scopes, and state value.&lt;/p&gt;

&lt;p&gt;Some providers require or allow you to add additional query parameters with their own semantics (like Google).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://accounts.google.com/o/oauth2/v2/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// the redirect URI must be an absolute URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;APPLICATION_BASE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;/oauth/callback/google`&lt;/span&gt;

&lt;span class="c1"&gt;// Scopes are usually things like 'channels:read' but other providers treat them like URLs. &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/drive.file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// Optional; more on this later.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect_uri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Denotes authorization code flow&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     

&lt;span class="c1"&gt;// Some providers use space-separation, others use `+` or `,`&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scope&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// Use space separation for Google OAuth&lt;/span&gt;

&lt;span class="c1"&gt;// Google-specific fields&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;access_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;offline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Request refresh token&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;consent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Force consent to ensure refresh token&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorizationUrlForUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have an authorization URL for the user with all the correct query parameters, you should redirect the user to it.&lt;/p&gt;

&lt;p&gt;URL construction will vary for each provider depending on their base URL, scope separation conventions, and any additional required or optional query parameters.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Authorization Code Exchange
&lt;/h3&gt;

&lt;p&gt;Once the user is redirected to the authorization URL and grants consent, they will be redirected back to that callback URL / redirect URL with the &lt;code&gt;state&lt;/code&gt; parameter you specified, and the &lt;code&gt;code&lt;/code&gt; (the authorization code).&lt;/p&gt;

&lt;p&gt;Once you have the authorization code, you need to exchange it for an access token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;GoogleTokenResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;id_token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exchangeGoogleAuthCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GoogleTokenResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grant_type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect_uri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// the origiunal redirect URI you specified in the authorization URL&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Note: you will want to save all of this information&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveAllThisInformation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// TODO - something that makes sense for your application. redirect them to the dashbord and show a toast or something.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error exchanging auth code:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example of making that request in cURL for the non-typescript users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://oauth2.googleapis.com/token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"code=YOUR_AUTH_CODE"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_id=YOUR_GOOGLE_CLIENT_ID"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_secret=YOUR_GOOGLE_CLIENT_SECRET"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"redirect_uri=https://your-app.com/oauth/callback/google"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=authorization_code"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are lots of "gotchas" here: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Content types&lt;/strong&gt;: different OAuth providers will require &amp;amp; support different content types for token exchanges (and token refreshes). The most common one, shown in the specification, is &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. However, some providers support / require other formats, including &lt;code&gt;application/json&lt;/code&gt; and &lt;code&gt;multipart/form-data&lt;/code&gt;. When I was implementing this flow for a number of different OAuth providers, I ran into all three of these for request content types, though most by default returned responses as &lt;code&gt;application/json&lt;/code&gt;. But watch out for this! &lt;em&gt;Each provider may do it differently&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access token lifespan&lt;/strong&gt;: at a minumum, an OAuth provider should return an &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;token_type&lt;/code&gt; field, named as such. If this is all you receive, it's often the case that the access token doesn't expire. If the access token expires, the provider may also return an &lt;code&gt;expires_in&lt;/code&gt; field (in seconds, not milliseconds! That's a common gotcha.) and optionally a &lt;code&gt;refresh_token&lt;/code&gt; which is a long-lived or non-expiring token that can be used to obtain a new access token once the current one expires. Some providers like Linear issue super-long-lived access tokens (e.g., 10 years), effectively punting on the refresh token mechanism. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional content&lt;/strong&gt;: Many OAuth providers will provide &lt;em&gt;additional&lt;/em&gt; fields that contain provider-specific metadata, or things which pertain to various OAuth extensions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these need to be handled correctly on a per-provider basis!&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Token Refresh Management
&lt;/h3&gt;

&lt;p&gt;Most access tokens expire, which means you need to track when they expire and refresh them when needed. This requires storing refresh tokens securely and implementing a token refresh mechanism.&lt;/p&gt;

&lt;p&gt;Usually, refreshing an access token involves making a &lt;code&gt;POST&lt;/code&gt; request to the same token endpoint as in the token exchange, and requires providing your Client ID, Client Secret, the &lt;code&gt;grant_type&lt;/code&gt; set to &lt;code&gt;refresh_token&lt;/code&gt;, and the refresh token for the user whose access token you want to refresh. In most cases, providers use the &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; content type, but your mileage may vary as previously noted. Make sure to consult the docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TypeScript example for token refresh&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RefreshTokenRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RefreshTokenResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refreshGoogleAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;requireEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="nx"&gt;refresh_token&lt;/span&gt;
    &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;RefreshTokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// todo: save this information!&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error refreshing token:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# cURL example for Google OAuth token refresh&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://oauth2.googleapis.com/token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"refresh_token=YOUR_REFRESH_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_id=YOUR_GOOGLE_CLIENT_ID"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"client_secret=YOUR_GOOGLE_CLIENT_SECRET"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=refresh_token"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can either proactively refresh access tokens as soon as they expire, or you can refresh them on-demand when a user needs them and you find that they're expired – just know that doing so will create additional latency.&lt;/p&gt;

&lt;p&gt;Once again, different OAuth providers handle this exchange differently. Some give you only &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;expires_in&lt;/code&gt; and &lt;code&gt;token_type&lt;/code&gt;. Others will give you a &lt;strong&gt;new&lt;/strong&gt; refresh token as well, depending on additional fields you can add to the request.&lt;br&gt;
Still others will give you additional metadata. &lt;/p&gt;

&lt;h3&gt;
  
  
  6. Scope Management
&lt;/h3&gt;

&lt;p&gt;When your application requests user authorization, it must provide a list of scopes that it is asking for to the Authorization Server in the &lt;code&gt;scope&lt;/code&gt; query parameter to the authorization URL. Usually, you will specify a list of scopes as a single string, concatenated by spaces, commas, &lt;code&gt;+&lt;/code&gt; signs, or something similar. &lt;/p&gt;

&lt;p&gt;There are no "standard scopes" in OAuth - the list of possible scopes you may request depends completely on the OAuth provider and their services and system architecture. (There are, however, &lt;a href="https://openid.net/specs/openid-connect-basic-1_0.html#Scopes" rel="noopener noreferrer"&gt;standard scopes for OpenID Connect&lt;/a&gt;).&lt;br&gt;
Some OAuth providers' scopes look like simple words: &lt;code&gt;channels&lt;/code&gt;, &lt;code&gt;emails&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Others are more narrow and hierarchical: &lt;code&gt;channels:read&lt;/code&gt;, &lt;code&gt;im:read&lt;/code&gt;, &lt;code&gt;im:write&lt;/code&gt;, and so forth.&lt;/p&gt;

&lt;p&gt;And still others model them as URLs: &lt;code&gt;https://www.googleapis.com/auth/drive.file&lt;/code&gt;, &lt;code&gt;https://www.googleapis.com/auth/cloud-platform.read-only&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your application needs a list of the scopes that you want to request from the provider when you initiate the OAuth flow. &lt;/p&gt;

&lt;p&gt;Based on the scopes you're requesting, the Authorization server will usually display information about what privileges you're requesting (which are entailed by the scopes) to the user on the consent prompt. &lt;/p&gt;

&lt;p&gt;Some providers then &lt;em&gt;only&lt;/em&gt; give the user the option to approve or reject the authorization request. Others will allow the user to check boxes or otherwise select which permissions that you have requested they would like to grant. Still others allow you to specify a list of "required scopes" that your app needs to function, and "optional scopes" that you would like but which the user can choose not to grant while still authorizing your app for the required scopes. &lt;/p&gt;

&lt;p&gt;In these cases, the provider may return to you a list of scopes (in their own format, providers don't really follow a standardized way of doing this) in the response to the access token exchange or token refresh request, that indicates which scopes out of the ones that you requested your access token has actually been granted.&lt;/p&gt;

&lt;p&gt;In these cases, depending on your application, you may need to track which scopes you have been granted and which APIs / resources you are and are not able to use on the resource server based on the granted scopes. &lt;/p&gt;

&lt;p&gt;Obviously, this can get messy quickly when you're implementing lots of different providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. State Parameter and CSRF Protection
&lt;/h3&gt;

&lt;p&gt;So earlier, we mentioned but skipped over the &lt;code&gt;state&lt;/code&gt; parameter. "What is it, and why should I care about it if it's optional?", you might ask.&lt;/p&gt;

&lt;p&gt;Great question. The &lt;code&gt;state&lt;/code&gt; parameter is a **security feature which is used to prevent Cross-Site Request Forgery (CSRF) attacks, and which providers other benefits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purpose &amp;amp; Functionality&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CSRF Protection:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;When your application initiates the authorization code flow, the backend should generate a unique, random (sufficiently-long so as to be secure) &lt;code&gt;state&lt;/code&gt; value, and include it in the authorization server URL redirect. &lt;/li&gt;
&lt;li&gt;The state value should then also be set as a CSRF cookie (make sure to set the &lt;code&gt;SameSite&lt;/code&gt; property to &lt;code&gt;lax&lt;/code&gt;! Otherwise it won't work since Cookies with a &lt;code&gt;strict&lt;/code&gt; &lt;code&gt;SameSite&lt;/code&gt; property aren't included on requests that result from cross-domain redirects; which the callback is)&lt;/li&gt;
&lt;li&gt;The authorization server will include that same &lt;code&gt;state&lt;/code&gt; value as a query parameter in the redirect to your callback URL after the user grants consent. &lt;/li&gt;
&lt;li&gt;Your callback URL handler should &lt;strong&gt;verify&lt;/strong&gt; (a) that your server issued this state value, and (b) that the value in the URL matches the value in the CSRF cookie.&lt;/li&gt;
&lt;li&gt;If they don't match, the client should reject the response, to prevent unauthorized or tampered-with responses and session hijacking attacks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session Correlation&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;When your application creates a state value, it can store it in the database and associate it with the currently-authorized user, and with other relevant information.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;state&lt;/code&gt; value can then be used to correlate the callback with the initial redirect to the authorization URL. This is useful in scenarious where there are multiple users or sessions in the same brrowser at the same time.&lt;/li&gt;
&lt;li&gt;It can ensure that the response is processed in the context of the correct user session.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional Data&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;in your database , you can associate information with the token such as the URL in your application that you want the end-user to be redirected to after the callback is processed. &lt;/li&gt;
&lt;li&gt;it can be used to maintain other application context; however it should be an &lt;strong&gt;opaque&lt;/strong&gt; token since it may be exposed in logs. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your application securely generates the &lt;code&gt;state&lt;/code&gt; value on the backend before redirecting the user to the Authorization Server's authorization URL.&lt;/li&gt;
&lt;li&gt;(Optional) Your application may store the token in its' database with optional contextual information as mentioned above.&lt;/li&gt;
&lt;li&gt;Your application should set it as a cookie in the user's browser &lt;em&gt;before&lt;/em&gt; redireting them to the authorization server's authorization URL.&lt;/li&gt;
&lt;li&gt;Your application should include the value as the &lt;code&gt;state&lt;/code&gt; query parameter on the authorization URL that the user is redirected to.&lt;/li&gt;
&lt;li&gt;When your users receives the callback redirect from the authorization server, check the &lt;code&gt;state&lt;/code&gt; value to confirm that it's present, and that it matches the contents of the cookie you set.&lt;/li&gt;
&lt;li&gt;(optional) retrieve contextal application information and use it &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Generally, you should follow the following best practices for state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unique&lt;/strong&gt;: The &lt;code&gt;state&lt;/code&gt; value should be unique for every authorization request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random&lt;/strong&gt;: It should be securely random - i.e. of sufficient length and complexity that it is infeasible to guess. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure storage&lt;/strong&gt;: Set the &lt;code&gt;state&lt;/code&gt; value as a &lt;code&gt;HttpOnly&lt;/code&gt; cookie (make sure to set &lt;code&gt;SameSite&lt;/code&gt; to &lt;code&gt;lax&lt;/code&gt; - &lt;code&gt;strict&lt;/code&gt; won't work!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No sensitive data&lt;/strong&gt;: Avoid including any sensitive information in the token itself - any application-specific contextual information should be stored in the database in a manner that's assoicated with the token. Don't use signed JWTs or something which may expose information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always use it&lt;/strong&gt;: While the &lt;code&gt;state&lt;/code&gt; parameter is an &lt;em&gt;optional&lt;/em&gt; part of the specification, it's strongly recommended to use it to enhance your application's security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Secure Token Storage
&lt;/h3&gt;

&lt;p&gt;Using access tokens for user authentication through a library like &lt;a href="https://better-auth.com" rel="noopener noreferrer"&gt;Better-auth&lt;/a&gt; where you're requesting non-highly-sensitive scopes like &lt;code&gt;openid&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;profile&lt;/code&gt; through OpenID Connect is one thing. You should still care about these tokens (they give you PII!), but you would be storing these unencrypted in your database. &lt;/p&gt;

&lt;p&gt;But if you're building OAuth integrations, in many cases you're doing it so that your application can access your users' information in other systems – things like files, emails, contacts, or accounting and payroll information, financial information, CRM, helpdesk tickets, or other software systems.&lt;/p&gt;

&lt;p&gt;In these cases, you should think of access tokens as &lt;strong&gt;API Keys&lt;/strong&gt; that give you access to sensitive information, resources, and systems &lt;em&gt;on the user's behalf&lt;/em&gt;. The security considerations of this model are &lt;strong&gt;completely different&lt;/strong&gt; from user authentication with OpenID Connect. &lt;/p&gt;

&lt;p&gt;Similarly to how you (hopefully) wouldn't store your users' passwords or API keys unhashed in a database, you shouldn't store your users' sensitive access tokens unencrypted. &lt;/p&gt;

&lt;p&gt;Now you may be thinking, "My database provider users encryption-at-rest! I'm already doing this."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And you would be wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unless your threat model is someone walking into your cloud provider's datacenter and pulling out the hard drive that your database lives on and walking out with it, &lt;strong&gt;encryption at rest is security theatre&lt;/strong&gt;. Encryption at rest does not prevent most database compromises, and does not secure information from software hacks.&lt;/p&gt;

&lt;p&gt;So what should you do? The simple answer is &lt;strong&gt;field-level encryption&lt;/strong&gt; - securely encrypt access tokens before inserting them into the database, and decrypt them after you retrieve them from the database when you need to make an API call. Here's a &lt;a href="https://drizzle-encryption.vercel.app/" rel="noopener noreferrer"&gt;great example&lt;/a&gt; of how to do this with &lt;a href="https://orm.drizzle.team" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;, which can be generalized to other frameworks and languages. &lt;/p&gt;

&lt;p&gt;This will prevent someone who gets access to your database from stealing all of the access tokens, unless they are able to separately compromise your application's encryption key. &lt;/p&gt;

&lt;p&gt;Personally, I really like using &lt;a href="https://evervault.com" rel="noopener noreferrer"&gt;Evervault&lt;/a&gt; - they have a great drop-in relay-based solution that requires minimal code changes (just an encrypt-only API key and relay URLs) and which means that you don't ever have to manage keys. Evervault manages keys and encryption/decryption operations, while you manage business logic and encrypted data. &lt;br&gt;
This separation of concerns means that even if your application is entirely compromised, an attacker can't decrypt your data, since all they would get is encrypted data and an encrypt-only API key. And if evervault were compromised, the attacker would only get keys - not encrypted data. You would both have to be compromised for your data to be stolen.&lt;/p&gt;

&lt;p&gt;(Note: I do not work for Evervault, and this is not a paid promotion - I just really like their solution!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building OAuth integrations is significantly more complex than it initially appears. You need to implement and maintain at least eight distinct components, many of which differ for each provider you support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client credentials management&lt;/li&gt;
&lt;li&gt;Callback URL handling&lt;/li&gt;
&lt;li&gt;Initiating the OAuth flow&lt;/li&gt;
&lt;li&gt;Authorization code exchange&lt;/li&gt;
&lt;li&gt;Token refresh management&lt;/li&gt;
&lt;li&gt;Scope management&lt;/li&gt;
&lt;li&gt;State parameter and CSRF protection&lt;/li&gt;
&lt;li&gt;Secure token storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This complexity is why many developers turn to OAuth libraries or integration platforms. However, even with these tools, you still need to understand the underlying mechanics to debug issues and ensure you're implementing everything securely.&lt;/p&gt;

&lt;p&gt;Have you implemented OAuth in your applications? What challenges did you face? Let me know in the comments below!&lt;/p&gt;

&lt;p&gt;P.S.: we built &lt;a href="https://onedollaroauth.com" rel="noopener noreferrer"&gt;One Dollar OAuth&lt;/a&gt; to solve all of this complexity for you for just $1 per application! Check it it out at &lt;a href="https://onedollaroauth.com" rel="noopener noreferrer"&gt;OneDollarOauth.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>oauth</category>
      <category>security</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
