Claude-code-plugins miro-install-auth
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/miro-pack/skills/miro-install-auth" ~/.claude/skills/jeremylongshore-claude-code-plugins-miro-install-auth && rm -rf "$T"
manifest:
plugins/saas-packs/miro-pack/skills/miro-install-auth/SKILL.mdsource content
Miro Install & Auth
Overview
Set up the official
@mirohq/miro-api Node.js client and configure OAuth 2.0 authentication against the Miro REST API v2 (https://api.miro.com/v2/).
Prerequisites
- Node.js 18+
- A Miro account (Free, Business, or Enterprise)
- A Miro app created at https://developers.miro.com (Your apps > Create new app)
- Client ID, Client Secret, and OAuth redirect URI from the app settings
Instructions
Step 1: Install the Official SDK
# Official Miro Node.js client npm install @mirohq/miro-api # For Express-based OAuth callback server npm install express dotenv
Step 2: Configure OAuth 2.0 Credentials
# .env (NEVER commit — add to .gitignore) MIRO_CLIENT_ID=your_client_id MIRO_CLIENT_SECRET=your_client_secret MIRO_REDIRECT_URI=http://localhost:3000/auth/miro/callback MIRO_ACCESS_TOKEN= # Filled after OAuth flow MIRO_REFRESH_TOKEN= # Filled after OAuth flow
Miro uses standard OAuth 2.0 authorization code flow. Tokens expire in 3599 seconds (approximately 1 hour). Always store and use the refresh token.
Step 3: OAuth 2.0 Authorization Flow
// src/auth.ts import { Miro } from '@mirohq/miro-api'; import express from 'express'; // High-level client handles token management const miro = new Miro({ clientId: process.env.MIRO_CLIENT_ID!, clientSecret: process.env.MIRO_CLIENT_SECRET!, redirectUrl: process.env.MIRO_REDIRECT_URI!, // Storage adapter for tokens (implement for production) storage: { async get(userId: string) { // Return stored token for user return getTokenFromDB(userId); }, async set(userId: string, token) { // Persist token await saveTokenToDB(userId, token); }, }, }); const app = express(); // Step 1: Redirect user to Miro authorization page app.get('/auth/miro', (req, res) => { const authUrl = miro.getAuthUrl(); res.redirect(authUrl); }); // Step 2: Handle OAuth callback app.get('/auth/miro/callback', async (req, res) => { const { code } = req.query; if (!code || typeof code !== 'string') { return res.status(400).send('Missing authorization code'); } try { // Exchange code for access_token + refresh_token await miro.exchangeCodeForAccessToken('default-user', code); res.send('Miro connected successfully!'); } catch (err) { console.error('Token exchange failed:', err); res.status(500).send('Authentication failed'); } }); app.listen(3000, () => console.log('OAuth server at http://localhost:3000'));
Step 4: Direct API Access (Access Token Only)
For scripts and automation where you already have an access token:
// src/client.ts import { MiroApi } from '@mirohq/miro-api'; // Low-level stateless client — pass token directly const api = new MiroApi(process.env.MIRO_ACCESS_TOKEN!); // Verify connection by listing boards async function verifyConnection() { const boards = await api.getBoards(); console.log(`Connected! Found ${boards.body.data?.length ?? 0} boards`); return true; } verifyConnection().catch(console.error);
Step 5: Configure OAuth Scopes
In your Miro app settings (https://developers.miro.com), enable the scopes your app requires:
| Scope | Purpose | Required For |
|---|---|---|
| Read board data, items, members | GET endpoints |
| Create/update/delete boards and items | POST/PUT/PATCH/DELETE endpoints |
| Read team info and members | Team management |
| Manage team membership | Team provisioning |
| Read org structure | Enterprise features |
| Read user profile | User identification |
| Read audit logs | Enterprise compliance |
Token response after successful exchange:
{ "access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "bearer", "expires_in": 3599, "scope": "boards:read boards:write", "user_id": "1234567890", "team_id": "9876543210" }
Error Handling
| Error | HTTP Status | Cause | Solution |
|---|---|---|---|
| 403 | Missing OAuth scope | Add required scope in app settings and re-authorize |
| 401 | Access token expired | Use refresh token to get new access token |
| 400 | Auth code already used or expired | Restart OAuth flow from the beginning |
| 401 | Wrong client_id or client_secret | Verify credentials in Miro app settings |
| N/A | DNS/network failure | Check internet and firewall rules |
Token Refresh Pattern
async function refreshAccessToken(): Promise<string> { const response = await fetch('https://api.miro.com/v1/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', client_id: process.env.MIRO_CLIENT_ID!, client_secret: process.env.MIRO_CLIENT_SECRET!, refresh_token: process.env.MIRO_REFRESH_TOKEN!, }), }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const data = await response.json(); // Store new tokens process.env.MIRO_ACCESS_TOKEN = data.access_token; process.env.MIRO_REFRESH_TOKEN = data.refresh_token; return data.access_token; }
Resources
- Miro OAuth 2.0 Guide
- Permission Scopes Reference
- Miro Node.js Client
- @mirohq/miro-api on npm
- Troubleshoot OAuth 2.0
Next Steps
After successful auth, proceed to
miro-hello-world for your first board and item operations.