How to Handle OAuth Authentication in Claude Plugins: A Complete MCP Server Implementation Guide
Claude plugins implement OAuth 2.0 by declaring auth requirements in the plugin manifest, launching browser-based consent through the Claude UI, handling callbacks on a local MCP server, and storing tokens securely in the OS keychain for automatic refresh and seamless injection into API calls.
Claude plugins in the anthropics/claude-plugins-community repository communicate with external APIs through an MCP (Claude Code Micro-Connector Platform) server. When a plugin requires access to user data from third-party services like Gmail, Salesforce, or Stripe, it follows a standardized OAuth 2.0 pattern that remains zero-configuration for end users after the initial setup.
Declaring OAuth Requirements in plugin.json
Every OAuth-enabled Claude plugin begins by declaring authentication requirements in its manifest. In .claude-plugin/plugin.json, developers specify the provider, required scopes, and redirect URI that the local MCP server will use to receive callbacks.
According to the community repository patterns found in quickdesign/.claude-plugin/plugin.json and eli5/.claude-plugin/plugin.json, the manifest structure follows this format:
{
"name": "gmail-connector",
"version": "1.0.0",
"auth": {
"provider": "google",
"scopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.send"
],
"redirect_uri": "http://localhost:3000/oauth/callback"
},
"tools": [
{ "name": "list-threads", "handler": "listThreads.js" },
{ "name": "send-mail", "handler": "sendMail.js" }
]
}
The auth object signals to the Claude platform that this plugin requires OAuth authorization, while the scopes array defines the specific permissions the plugin requests from the identity provider.
Launching the OAuth Consent Flow
Plugins initiate the OAuth flow through a gateway command registered in the skill implementation. As documented in quickdesign/skills/quickdesign/SKILL.md, developers typically implement a /setup command that generates and prints a clickable authorization URL.
When users execute this command, the Claude UI displays a URL like https://accounts.google.com/o/oauth2/auth?... and automatically opens the system browser to the provider's consent page. This pattern ensures the only required user interaction is the initial consent click—after authorization, Claude agents can invoke tools without further credential prompts.
Handling OAuth Callbacks on the MCP Server
The MCP server runs a local HTTP endpoint—typically implemented with Express or Fastify—to receive the OAuth callback at the specified redirect URI. The callback handler extracts the authorization code and exchanges it for access and refresh tokens via the provider's token endpoint.
Here is a complete oauth-callback.js implementation as used in the community plugins:
const express = require('express')
const axios = require('axios')
const keytar = require('keytar')
const router = express.Router()
router.get('/oauth/callback', async (req, res) => {
const { code } = req.query
try {
const tokenResp = await axios.post('https://oauth2.googleapis.com/token', null, {
params: {
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: 'http://localhost:3000/oauth/callback'
}
})
const { access_token, refresh_token, expires_in } = tokenResp.data
// Store securely
await keytar.setPassword('claude-plugin', 'gmail-token', JSON.stringify({
access_token,
refresh_token,
expiry: Date.now() + expires_in * 1000
}))
res.send('✅ OAuth successful – you can now use Gmail tools in Claude.')
} catch (e) {
console.error(e)
res.status(500).send('❌ OAuth failed')
}
})
module.exports = router
This route receives the code parameter from the OAuth provider, exchanges it for credentials, and immediately delegates to the secure storage layer.
Secure Token Storage with OS Keychain
Claude plugins never store tokens as plaintext on the filesystem. Instead, they use the keytar library (or equivalent keychain bindings) to encrypt credentials in the operating system's native credential store—macOS Keychain, Windows Credential Manager, or Linux secret-service.
In the callback handler above, keytar.setPassword() serializes the token bundle as JSON and stores it under the service name claude-plugin with the account key gmail-token. This ensures that access_token and refresh_token values remain encrypted at the OS level and are accessible only to the user account that created them.
Automatic Token Refresh
The MCP server includes transparent refresh logic in the token-vault module. Before making API calls, the server checks the expiry timestamp stored with the token. If the token expires within 60 seconds or has already expired, the server automatically calls the provider's refresh endpoint using the stored refresh_token, updates the encrypted storage with the new credentials, and proceeds with the request.
This interceptor pattern prevents authentication errors during tool execution and maintains seamless operation without requiring users to re-authenticate.
Using OAuth Tokens in API Calls
Every tool defined in the MCP server retrieves stored tokens from the vault and includes them in the Authorization: Bearer <token> header. The following listThreads.js example demonstrates retrieving and using the token:
const keytar = require('keytar')
const axios = require('axios')
module.exports = async () => {
const tokenStr = await keytar.getPassword('claude-plugin', 'gmail-token')
const { access_token, refresh_token, expiry } = JSON.parse(tokenStr)
// Refresh if needed (simplified)
if (Date.now() > expiry - 60000) {
// ... call refresh endpoint and update keytar (omitted for brevity)
}
const resp = await axios.get(
'https://gmail.googleapis.com/gmail/v1/users/me/threads',
{ headers: { Authorization: `Bearer ${access_token}` } }
)
return resp.data.threads.map(t => t.id)
}
The tool retrieves the encrypted token from the OS keychain, validates the expiry timestamp, and attaches the bearer token to the outgoing HTTP request.
Summary
- Declare OAuth in
plugin.json: Specify provider, scopes, and redirect URI in the.claude-plugin/plugin.jsonmanifest, following examples inquickdesignandeli5plugins. - Launch via gateway command: Use a
/setupskill command to generate the authorization URL and open the browser, as documented inquickdesign/skills/quickdesign/SKILL.md. - Handle callbacks locally: Implement an Express or Fastify route at
/oauth/callbackto exchange authorization codes for tokens. - Store in OS keychain: Use
keytarto encrypt tokens in macOS Keychain, Windows Credential Manager, or Linux secret-service. - Refresh automatically: Check token expiry before API calls and transparently refresh using the provider's token endpoint when necessary.
- Reference implementations: Consult
.claude-plugin/marketplace.jsonfor the complete list of OAuth-enabled plugins and their configurations.
Frequently Asked Questions
How do I declare OAuth scopes in a Claude plugin?
Declare OAuth requirements in your .claude-plugin/plugin.json file by adding an auth object containing the provider name, a scopes array of permission strings, and the redirect_uri where your MCP server listens. The manifest format supports major providers like Google, Salesforce, and Stripe, with each scope defining specific API access rights.
Where are OAuth tokens stored in Claude plugins?
OAuth tokens are stored in the operating system's native credential store using libraries like keytar or keychain. This approach encrypts tokens in macOS Keychain, Windows Credential Manager, or Linux secret-service, ensuring they never exist as plaintext on the filesystem and remain accessible only to the local user account.
How does automatic token refresh work in Claude plugins?
The MCP server checks the expiry timestamp on stored tokens before executing API calls. If a token expires within 60 seconds or has already expired, the server automatically calls the OAuth provider's refresh endpoint using the stored refresh_token, updates the encrypted keychain entry with new credentials, and proceeds with the request without user intervention.
What is the role of the MCP server in OAuth authentication?
The MCP (Claude Code Micro-Connector Platform) server serves as the local OAuth client, hosting the callback handler that exchanges authorization codes for tokens, managing secure storage and retrieval through the OS keychain, intercepting tool invocations to inject bearer tokens, and handling automatic refresh cycles to maintain valid sessions indefinitely.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →