Testing Your Integration
This guide helps you test your OAuth integration before going live.
Testing Checklist
Set Up Test Environment
Create a test account and register your app for testing.
Test Authorization Flow
Verify the complete OAuth flow works end-to-end.
Test API Calls
Make API requests using the obtained tokens.
Test Token Refresh
Verify refresh token rotation works correctly.
Test Error Handling
Ensure your app handles errors gracefully.
1. Set Up Test Environment
Create a Test Account
- Go to jmpy.me/signup
- Create an account with a test email
- 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:
- You see the JMPY login page (if not logged in)
- After login, you see the authorization consent page
- The page shows your app name and requested permissions
- 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:
- Import the collection from:
https://jmpy.me/.well-known/postman.json
- Set environment variables:
client_id - Your OAuth client ID
client_secret - Your OAuth client secret
redirect_uri - Your callback URL
- 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
| Issue | Cause | Solution |
|---|
invalid_redirect_uri | Redirect URI doesn’t match registered | Use exact URI from registration |
invalid_grant | Code already used or expired | Get a new authorization code |
| Token rejected | Token expired | Refresh the token |
| Scope error | Missing required scope | Request the scope in authorization |
Production Checklist
Before going live:
Next Steps