magicapp-connect (widgets)

Modified on Wed, 1 Apr at 5:57 AM

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 

AttributeTypeRequiredDescriptionDefault
client-idstringYesYour registered client ID provided by MAGICapp-
themestringNoUI theme for the connect button"default"


Theme Options 

  • default - Blue theme with standard styling
  • dark - 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 


Test 


Local Development 


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 

  1. User sees the "Connect to The MAGICApp" button
  2. User clicks the button
  3. Browser redirects to Keycloak for authentication
  4. User logs in or registers (if no account exists)
  5. User sees the consent screen and approves access
  6. Browser redirects back to your app
  7. Component exchanges authorization code for tokens
  8. Component displays the "Connected" status with a disconnect button
  9. 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_:

KeyDescriptionLifetime
magicapp_oauth_access_tokenAccess token for API requests15 minutes
magicapp_oauth_refresh_tokenToken for obtaining new access tokens30 days (sliding window)
magicapp_oauth_token_expiryUnix timestamp when access token expires-
magicapp_oauth_id_tokenOpenID Connect ID token-


Security Note: Using sessionStorage means tokens are automatically cleared when the browser tab closes, providing better security than localStorage.


Token Lifecycle 

  1. Initial Connection: User authenticates → receives access token (15 min) + refresh token (30 days)
  2. Making API Calls: Access token is automatically included in all API requests
  3. Token Expires: After 15 minutes, the access token becomes invalid
  4. Auto Refresh: Component automatically uses a refresh token to obtain a new access token
  5. Refresh Token Expiry: After 30 days of inactivity, the refresh token expires → the user must reconnect
  6. 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-connect element using document.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 MessageCauseSolution
State mismatch - possible CSRF attackState parameter doesn't match stored valueRestart authentication flow
No code verifier foundPKCE verifier missing from sessionStorageRestart authentication flow
Token exchange failedInvalid or expired authorization codeCode expired or already used - restart auth
Token refresh failedRefresh token expired or invalidUser needs to reconnect
Authentication expired. Please reconnect.Both tokens expiredUser needs to reconnect
APIBuilder is not loadedapiBuilder.js not imported before connectServiceComponent.jsCheck 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:

  1. PKCE (Proof Key for Code Exchange): Protects against authorization code interception attacks
  2. State Parameter: Prevents CSRF (Cross-Site Request Forgery) attacks
  3. sessionStorage: Tokens automatically cleared when browser tab closes
  4. Automatic Token Refresh: Minimizes exposure window for expired tokens
  5. HTTPS Only: All production traffic uses encrypted connections
  6. No Client Secrets: Public client design - no secrets exposed in frontend code


Security Best Practices 

  1. Always use HTTPS in production environments
  2. Register specific redirect URIs - avoid wildcards
  3. Monitor the error event for potential security issues
  4. Clear tokens on logout using disconnect()
  5. Don't log tokens to console in production
  6. Validate API responses before using data
  7. Handle token expiry gracefully - provide clear user feedback


Browser Compatibility 

The component requires modern browser features:

  • ES6 Modulesimport/export syntax
  • 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:

  1. 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
  2. Receive your client ID and test environment credentials
  3. Test your integration in the test environment
  4. 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 error events to catch authentication issues
  • Verify API endpoints are correct for your environment

Support 

Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article