Overview
The magicapp-connect component is a web component that handles OAuth 2.0 authentication with MAGICapp's API. It automatically manages the complete authentication flow, token storage, and token refresh, providing a simple interface for third-party developers to integrate with MAGICapp.
Installation
Include the required JavaScript files from the CDN:
<script type="module">
import 'https://app.magicapp.org/widget/connectServiceComponent.js';
</script>
Basic Usage
HTML Markup
<magicapp-connect client-id="your-client-id"></magicapp-connect>
With Event Handlers
<magicapp-connect client-id="your-client-id"></magicapp-connect>
<script type="module">
const connector = document.querySelector('magicapp-connect');
// Listen for connection events
connector.addEventListener('connected', (e) => {
console.log('Connected!', e.detail);
loadData();
});
connector.addEventListener('disconnected', () => {
console.log('Disconnected from MAGICapp');
});
connector.addEventListener('error', (e) => {
console.error('Error:', e.detail.message);
});
</script>
Component Attributes
| Attribute | Type | Required | Description | Default |
|---|---|---|---|---|
client-id | string | Yes | Your registered client ID provided by MAGICapp | - |
theme | string | No | UI theme for the connect button | "default" |
Theme Options
default- Blue theme with standard stylingdark- Dark theme with lighter colors
<!-- Default theme -->
<magicapp-connect client-id="your-client-id"></magicapp-connect>
<!-- Dark theme -->
<magicapp-connect client-id="your-client-id" theme="dark"></magicapp-connect>
Automatic Configuration
The component automatically configures authentication and API endpoints based on your environment:
Production
- Hostname:
app.magicapp.org - Keycloak URL: https://auth.magicapp.org
- API Base URL: https://api.magicapp.org
Test
- Hostname:
app-test.magicapp.org - Keycloak URL: https://auth-test.magicapp.org
- API Base URL: https://api-test.magicapp.org
Local Development
- Hostname:
localhost - Keycloak URL: http://localhost:8180
- API Base URL: http://localhost:8080
Additional automatic settings:
- Realm: Always
magicapp - Service Name: Always
"The MAGICApp" - Redirect URI: Automatically set to
window.location.origin + window.location.pathname
Properties
config
Returns the current configuration object:
const connector = document.querySelector('magicapp-connect');
console.log(connector.config);
// {
// realm: 'magicapp',
// serviceName: 'The MAGICApp',
// clientId: 'your-client-id',
// keycloakUrl: 'https://auth.magicapp.org',
// redirectUri: 'https://yourapp.com/page',
// apiBaseUrl: 'https://api.magicapp.org',
// theme: 'default'
// }
api
Returns an APIBuilder instance for making API calls. See the APIBuilder documentation for details.
const connector = document.querySelector('magicapp-connect');
const guidelines = await connector.api.guidelines.getAll();
Methods
isConnected()
Check if the user is currently connected.
const connector = document.querySelector('magicapp-connect');
if (connector.isConnected()) {
console.log('User is connected');
} else {
console.log('User needs to connect');
}
Returns: boolean
getAccessToken()
Get the current access token. Automatically refreshes if expired or expiring soon (within 60 seconds).
const connector = document.querySelector('magicapp-connect');
const token = await connector.getAccessToken();
if (token) {
console.log('Valid token available');
} else {
console.log('No valid token - user needs to connect');
}
Returns: Promise<string | null>
Automatic Refresh: This method will automatically:
- Use the refresh token if the access token is missing
- Refresh the token if it's expired
- Refresh the token if it will expire within 60 seconds
disconnect()
Disconnect the user and clear all stored tokens.
const connector = document.querySelector('magicapp-connect');
connector.disconnect();
Returns: void
apiRequest(endpoint, options)
Make an authenticated API request. Automatically includes the access token in the Authorization header.
const connector = document.querySelector('magicapp-connect');
const data = await connector.apiRequest('/api/v1/guidelines', {
method: 'GET'
});
const created = await connector.apiRequest('/api/v1/resource', {
method: 'POST',
body: JSON.stringify({ name: 'Example' })
});
Parameters:
endpoint(string): API endpoint path (e.g.,/api/v1/guidelines)options(object): Fetch API options (method, headers, body, etc.)
Returns: Promise<any> - Parsed JSON response
Note: If no access token is available, the request is made as a public user (without authentication).
Events
connected
Fired when the user successfully authenticates and tokens are received.
connector.addEventListener('connected', (event) => {
console.log('Access Token:', event.detail.tokens.access_token);
console.log('Refresh Token:', event.detail.tokens.refresh_token);
console.log('Expires In:', event.detail.tokens.expires_in, 'seconds');
console.log('ID Token:', event.detail.tokens.id_token);
// Now safe to make API calls
loadData();
});
Event Detail:
{
tokens: {
access_token: string,
refresh_token: string,
expires_in: number, // 900 seconds (15 minutes)
token_type: "Bearer",
id_token: string,
scope: string
}
}
disconnected
Fired when the user disconnects or logs out.
connector.addEventListener('disconnected', () => {
console.log('User has disconnected');
// Clear UI, redirect to login page, etc.
});
error
Fired when an error occurs during authentication or token operations.
connector.addEventListener('error', (event) => {
console.error('Error Message:', event.detail.message);
console.error('Error Object:', event.detail.error);
// Handle specific errors
if (event.detail.message.includes('State mismatch')) {
alert('Security error: possible CSRF attack detected');
}
});
Event Detail:
{
message: string, // Error message
error: Error // Original error object
}
Authentication Flow
User Experience
- User sees the "Connect to The MAGICApp" button
- User clicks the button
- Browser redirects to Keycloak for authentication
- User logs in or registers (if no account exists)
- User sees the consent screen and approves access
- Browser redirects back to your app
- Component exchanges authorization code for tokens
- Component displays the "Connected" status with a disconnect button
- Your app can now make authenticated API calls
Technical Details
The component automatically handles:
- PKCE (Proof Key for Code Exchange) generation
- State parameter for CSRF protection
- Authorization code exchange
- Token storage in sessionStorage
- Automatic token refresh (60 seconds before expiry)
- Query parameter preservation during OAuth redirect
Token Management
Token Storage
Tokens are stored in sessionStorage with the prefix magicapp_oauth_:
| Key | Description | Lifetime |
|---|---|---|
magicapp_oauth_access_token | Access token for API requests | 15 minutes |
magicapp_oauth_refresh_token | Token for obtaining new access tokens | 30 days (sliding window) |
magicapp_oauth_token_expiry | Unix timestamp when access token expires | - |
magicapp_oauth_id_token | OpenID Connect ID token | - |
Security Note: Using sessionStorage means tokens are automatically cleared when the browser tab closes, providing better security than localStorage.
Token Lifecycle
- Initial Connection: User authenticates → receives access token (15 min) + refresh token (30 days)
- Making API Calls: Access token is automatically included in all API requests
- Token Expires: After 15 minutes, the access token becomes invalid
- Auto Refresh: Component automatically uses a refresh token to obtain a new access token
- Refresh Token Expiry: After 30 days of inactivity, the refresh token expires → the user must reconnect
- Sliding Window: Each time a refresh token is used, it's renewed for another 30 days
Automatic Token Refresh
The component automatically refreshes access tokens in these scenarios:
- Access token is missing, but the refresh token exists
- Access token is expired
- Access token will expire within 60 seconds
You don't need to manually handle token refresh - it happens automatically when you call getAccessToken() or make API requests via apiRequest() or the API builder.
Query Parameter Preservation
The component automatically preserves your URL query parameters during the OAuth redirect flow:
Before OAuth:
https://yourapp.com/page?guideline=123&recommendation=456&lang=en
During OAuth (temporary):
https://yourapp.com/page?code=AUTH_CODE&state=STATE&session_state=...
After OAuth (restored):
https://yourapp.com/page?guideline=123&recommendation=456&lang=en
This ensures your application state is maintained through the authentication flow, allowing your components (like magicapp-recommendation) to read their parameters after login completes.
Using with Other Components
The magicapp-connect component provides authentication for other MAGICapp widgets. Other components (like magicapp-recommendation and magicapp-pico) automatically detect and use the connector's authentication when present on the same page:
<script type="module">
import 'https://app.magicapp.org/widget/apiBuilder.js';
import 'https://app.magicapp.org/widget/connectServiceComponent.js';
import 'https://app.magicapp.org/widget/recommendationComponent.js';
</script>
<magicapp-connect client-id="your-client-id"></magicapp-connect>
<!-- Component anywhere in the same DOM - automatically uses connector's auth -->
<div class="content">
<magicapp-recommendation
guideline="jW0XmL"
recommendation="Lwr3VG">
</magicapp-recommendation>
</div>
How it works:
- Components search the DOM for a
magicapp-connectelement usingdocument.querySelector('magicapp-connect') - If a connector is found and connected, components automatically use its access token
- If no connector is found, components make unauthenticated (public) API calls
- Components can be anywhere in the DOM - nesting is not required
Alternative: Nested Components
You can also nest components inside the connector, which works the same way:
<magicapp-connect client-id="your-client-id">
<magicapp-recommendation
guideline="jW0XmL"
recommendation="Lwr3VG">
</magicapp-recommendation>
</magicapp-connect>
Both approaches work identically - use whichever fits your page structure better.
Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MAGICapp Integration</title>
<style>
body {
font-family: system-ui, sans-serif;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.status {
padding: 15px;
margin: 20px 0;
border-radius: 4px;
}
.status.connected {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.disconnected {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.guideline {
margin: 15px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
<script type="module">
import 'https://app.magicapp.org/widget/apiBuilder.js';
import 'https://app.magicapp.org/widget/connectServiceComponent.js';
</script>
</head>
<body>
<h1>MAGICapp Integration Demo</h1>
<magicapp-connect client-id="my-app-client-id"></magicapp-connect>
<div id="status"></div>
<div id="content"></div>
<script type="module">
const connector = document.querySelector('magicapp-connect');
const statusEl = document.getElementById('status');
const contentEl = document.getElementById('content');
// Handle connection state changes
connector.addEventListener('connected', (e) => {
statusEl.className = 'status connected';
statusEl.innerHTML = `
<strong>✓ Connected to MAGICapp</strong><br>
<small>Token expires in ${e.detail.tokens.expires_in} seconds</small>
`;
loadGuidelines();
});
connector.addEventListener('disconnected', () => {
statusEl.className = 'status disconnected';
statusEl.innerHTML = '<strong>⚠ Disconnected from MAGICapp</strong>';
contentEl.innerHTML = '';
});
connector.addEventListener('error', (e) => {
statusEl.className = 'status error';
statusEl.innerHTML = `<strong>✗ Error:</strong> ${e.detail.message}`;
});
// Check initial connection state
if (connector.isConnected()) {
statusEl.className = 'status connected';
statusEl.innerHTML = '<strong>✓ Already connected</strong>';
loadGuidelines();
} else {
statusEl.className = 'status disconnected';
statusEl.innerHTML = '<strong>Click the button above to connect</strong>';
}
// Load guidelines using API
async function loadGuidelines() {
try {
contentEl.innerHTML = '<p>Loading guidelines...</p>';
const guidelines = await connector.api.guidelines.getAll();
contentEl.innerHTML = '<h2>Available Guidelines</h2>';
guidelines.slice(0, 5).forEach(g => {
const div = document.createElement('div');
div.className = 'guideline';
div.innerHTML = `
<h3>${g.name || g.title}</h3>
<p><strong>Short Code:</strong> ${g.shortCode}</p>
`;
contentEl.appendChild(div);
});
} catch (error) {
contentEl.innerHTML = `<p class="status error">Error: ${error.message}</p>`;
}
}
</script>
</body>
</html>
Error Handling
Common Errors
| Error Message | Cause | Solution |
|---|---|---|
State mismatch - possible CSRF attack | State parameter doesn't match stored value | Restart authentication flow |
No code verifier found | PKCE verifier missing from sessionStorage | Restart authentication flow |
Token exchange failed | Invalid or expired authorization code | Code expired or already used - restart auth |
Token refresh failed | Refresh token expired or invalid | User needs to reconnect |
Authentication expired. Please reconnect. | Both tokens expired | User needs to reconnect |
APIBuilder is not loaded | apiBuilder.js not imported before connectServiceComponent.js | Check script import order |
Error Handling Best Practices
const connector = document.querySelector('magicapp-connect');
// Listen for authentication errors
connector.addEventListener('error', (e) => {
console.error('Auth Error:', e.detail.message);
if (e.detail.message.includes('State mismatch')) {
alert('Security error detected. Please try connecting again.');
} else if (e.detail.message.includes('Token exchange failed')) {
alert('Authentication failed. Please try again.');
}
});
// Handle API call errors
async function loadData() {
try {
const data = await connector.api.guidelines.getAll();
displayData(data);
} catch (error) {
if (error.message.includes('reconnect')) {
alert('Your session has expired. Please reconnect.');
connector.disconnect();
} else if (error.message.includes('Not connected')) {
alert('Please connect to MAGICapp first.');
} else {
console.error('API Error:', error);
alert('Failed to load data. Please try again.');
}
}
}
Security
Security Features
The component implements several security best practices:
- PKCE (Proof Key for Code Exchange): Protects against authorization code interception attacks
- State Parameter: Prevents CSRF (Cross-Site Request Forgery) attacks
- sessionStorage: Tokens automatically cleared when browser tab closes
- Automatic Token Refresh: Minimizes exposure window for expired tokens
- HTTPS Only: All production traffic uses encrypted connections
- No Client Secrets: Public client design - no secrets exposed in frontend code
Security Best Practices
- Always use HTTPS in production environments
- Register specific redirect URIs - avoid wildcards
- Monitor the error event for potential security issues
- Clear tokens on logout using
disconnect() - Don't log tokens to console in production
- Validate API responses before using data
- Handle token expiry gracefully - provide clear user feedback
Browser Compatibility
The component requires modern browser features:
- ES6 Modules:
import/exportsyntax - Custom Elements: Web Components v1 API
- Shadow DOM: For style encapsulation
- Crypto API: For PKCE generation (
crypto.subtle.digest) - Fetch API: For HTTP requests
- sessionStorage: For token storage
Supported Browsers
- Chrome/Edge 80+
- Firefox 75+
- Safari 13.1+
- Opera 67+
Getting Your Client ID
To use the magicapp-connect component, you need to register your application:
- Email api-support@magicapp.org with:
- Application name and description
- Your application URL(s) and redirect URIs
- Application type (web app, mobile, etc.)
- Expected usage and integration details
- Receive your client ID and test environment credentials
- Test your integration in the test environment
- Deploy to production using the same client ID
Troubleshooting
Component not rendering
- Verify scripts are loaded with
type="module" - Check browser console for import errors
- Ensure client-id attribute is set
OAuth redirect not working
- Verify redirect URI matches your page URL exactly
- Check that redirect URI is registered with MAGICapp
- Ensure no typos in client-id
Tokens not persisting
- Expected behavior: sessionStorage is cleared on tab close
- For persistent sessions, users must reconnect in new browser sessions
- This is intentional for security
API calls failing
- Check if user is connected:
connector.isConnected() - Listen for
errorevents to catch authentication issues - Verify API endpoints are correct for your environment
Support
- Email: api-support@magicapp.org
- Documentation: https://help.magicapp.org
- Status: https://status.magicapp.org
- Examples: https://magicevidence.org/widget-test-auth
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article