Testing Your Integration

This guide helps you test your OAuth integration before going live.

Testing Checklist

1

Set Up Test Environment

Create a test account and register your app for testing.
2

Test Authorization Flow

Verify the complete OAuth flow works end-to-end.
3

Test API Calls

Make API requests using the obtained tokens.
4

Test Token Refresh

Verify refresh token rotation works correctly.
5

Test Error Handling

Ensure your app handles errors gracefully.

1. Set Up Test Environment

Create a Test Account

  1. Go to jmpy.me/signup
  2. Create an account with a test email
  3. Verify your email address

Register Your Test App

curl -X POST https://jmpy.me/mcp/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My App (Development)",
    "redirect_uris": ["http://localhost:3000/callback"],
    "scope": "shorturl:read shorturl:create qrcode:read qrcode:create"
  }'
For local development, you can use localhost URLs. For production, you must use HTTPS.

2. Test Authorization Flow

Manual Browser Test

Build the authorization URL and open it in your browser:
const authUrl = new URL('https://jmpy.me/mcp/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'http://localhost:3000/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'shorturl:read shorturl:create');
authUrl.searchParams.set('state', 'test_state_123');

console.log(authUrl.toString());
// Open this URL in your browser
Expected behavior:
  1. You see the JMPY login page (if not logged in)
  2. After login, you see the authorization consent page
  3. The page shows your app name and requested permissions
  4. Clicking “Allow” redirects to your callback URL with a code parameter

Using cURL for Token Exchange

After getting the authorization code from the callback:
curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=YOUR_AUTH_CODE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=http://localhost:3000/callback"
Expected response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "jmpy_rt_...",
  "scope": "shorturl:read shorturl:create"
}

3. Test API Calls

Create a Short URL

curl -X POST https://jmpy.me/api/v1/urls \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/test-page"
  }'
Expected response:
{
  "success": true,
  "url": {
    "id": "abc123",
    "shortUrl": "https://jmpy.me/abc123",
    "originalUrl": "https://example.com/test-page",
    "createdAt": "2024-01-10T12:00:00Z"
  }
}

Generate a QR Code

curl -X POST https://jmpy.me/api/v1/qr \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "https://jmpy.me/abc123",
    "format": "png"
  }'

Test Scope Restrictions

Try accessing an endpoint that requires a scope you didn’t request:
# If you don't have analytics:read scope
curl https://jmpy.me/api/v1/analytics/abc123 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Expected error:
{
  "success": false,
  "error": "Insufficient scope",
  "required_scope": "analytics:read"
}

4. Test Token Refresh

Wait for Expiration or Force Refresh

Access tokens expire after 1 hour. Test refresh before going live:
curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
Important checks:
  • ✅ New access token is returned
  • ✅ New refresh token is returned (token rotation)
  • ✅ Old refresh token no longer works

Test Refresh Token Reuse Detection

Try using the old refresh token again:
# Using the OLD refresh token (should fail)
curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=OLD_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
Expected error:
{
  "error": "invalid_grant",
  "error_description": "Refresh token has been revoked or already used"
}

5. Test Error Handling

Invalid Client Credentials

curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=valid_code" \
  -d "client_id=invalid_client" \
  -d "client_secret=wrong_secret"
Expected: invalid_client error

Expired Authorization Code

Authorization codes expire after 10 minutes. Test by waiting or using an old code. Expected: invalid_grant error with “expired” message

Invalid Redirect URI

curl -X POST https://jmpy.me/mcp/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=valid_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=https://different-domain.com/callback"
Expected: invalid_grant error with “redirect_uri mismatch” message

Simple Test Server

Here’s a minimal Express.js server for testing the OAuth flow:
const express = require('express');
const app = express();

const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const REDIRECT_URI = 'http://localhost:3000/callback';

app.get('/login', (req, res) => {
  const authUrl = new URL('https://jmpy.me/mcp/oauth/authorize');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'shorturl:read shorturl:create');
  authUrl.searchParams.set('state', 'test_state');
  res.redirect(authUrl.toString());
});

app.get('/callback', async (req, res) => {
  const { code, state, error } = req.query;
  
  if (error) {
    return res.send(`Error: ${error}`);
  }
  
  // Exchange code for token
  const tokenResponse = await fetch('https://jmpy.me/mcp/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: REDIRECT_URI
    })
  });
  
  const tokens = await tokenResponse.json();
  res.json(tokens);
});

app.listen(3000, () => {
  console.log('Test server running at http://localhost:3000');
  console.log('Visit http://localhost:3000/login to start OAuth flow');
});

Using Postman

We provide a Postman collection for testing:
  1. Import the collection from: https://jmpy.me/.well-known/postman.json
  2. Set environment variables:
    • client_id - Your OAuth client ID
    • client_secret - Your OAuth client secret
    • redirect_uri - Your callback URL
  3. Run the “Authorization Code Flow” folder in order

Debugging Tips

Check JWT Token Contents

Decode your access token at jwt.io to verify:
{
  "sub": "user_uid",
  "client_id": "jmpy_client_abc123",
  "scope": "shorturl:read shorturl:create",
  "iat": 1704931200,
  "exp": 1704934800
}

Common Issues

IssueCauseSolution
invalid_redirect_uriRedirect URI doesn’t match registeredUse exact URI from registration
invalid_grantCode already used or expiredGet a new authorization code
Token rejectedToken expiredRefresh the token
Scope errorMissing required scopeRequest the scope in authorization

Production Checklist

Before going live:
  • Use HTTPS redirect URIs
  • Store client secret securely (environment variables, secrets manager)
  • Implement proper token storage (encrypted, not in localStorage)
  • Handle token refresh automatically
  • Implement proper error handling and retry logic
  • Test with real user accounts
  • Review requested scopes (request minimum necessary)

Next Steps