---
title: SSO-Manager
slug: experience/sso-manager
docTags: U4gHdNebGGoMU4rUtx-Hw,z8-QaXm-4AA8vr6q10eP9,Xogy-nAy3ZW4ooGTDDhXc
createdAt: 2025-12-10T12:59:01.039Z
---

# SSO Manager Documentation

## Overview

The SSO Manager provides a unified authentication flow for both web and native app platforms. It handles OAuth and token-based authentication with external SSO providers, supporting multiple trigger mechanisms including URL parameters and postMessage communication.

**Singleton Pattern**: The SSOManager class is a singleton, ensuring only one authentication instance exists throughout the application lifecycle.

### Key Features

- **Dual Platform Support**: Works seamlessly on both web and native apps
- **Multiple Trigger Methods**: URL parameters, postMessage (iframe integration)
- **Automatic State Detection**: Determines login/logout requirements based on SSO provider responses
- **Configurable**: Highly customizable through a central configuration file
- **Return URL Handling**: Automatically manages return navigation after authentication
- **Singleton Architecture**: Built-in singleton pattern ensures consistent state management

## File Structure

:::BlockQuote
default/storefront/assets/scripts/custom/
├── custom.js                      # Main entry point - import init.js here
└── sso-manager/
&#x20;   ├── config.js                  # Configuration file
&#x20;   ├── sso-manager.js             # Core SSOManager class
&#x20;   └── init.js                    # Extended class with lifecycle hooks
:::

## Installation & Setup

### 1. Import in custom.js

Add the import to your main custom.js file:

**File: default/storefront/assets/scripts/custom/custom.js**

:::BlockQuote
import './sso-manager/init.js';
:::

This will automatically initialize the SSO Manager when Purple Service is ready.

### 2. Access the SSO Manager

:::BlockQuote
// Import the singleton instance (from anywhere in your code)
import \{ ssoManager } from './sso-manager/init.js';

// Wait for initialization, then use
ssoManager.login();

// You can also use the base class directly if needed
import \{ SSOManager } from './sso-manager/sso-manager.js';
const instance = SSOManager.getInstance();
:::

**Important**: Due to the singleton pattern, multiple instantiations return the same instance:

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';

const instance1 = new SSOManager();
const instance2 = new SSOManager();
const instance3 = SSOManager.getInstance();
console.log(instance1 === instance2 === instance3); // true
:::

### 3. Configure

Edit config.js to match your environment:

**File: default/storefront/assets/scripts/custom/sso-manager/config.js**

:::BlockQuote
export const config = \{
&#x20; type: "oAuth",
&#x20; withPostMessage: false,
&#x20; externalState: \{
&#x20;   login: \["logged\_in", "true"],
&#x20;   logout: \["logged\_in", "false"],
&#x20;   purchase: \["purchase", "true"],
&#x20;   token: \["transferToken"]
&#x20; },
&#x20; urlParamKeys: \{
&#x20;   returnUrl: "returnTo",
&#x20;   loginViaUrl: "external\_login",
&#x20;   checkoutViaUrl: "external\_checkout",
&#x20;   logoutViaUrl: "external\_logout",
&#x20; },
&#x20; postMessage: \{
&#x20;   validOrigins: \[
&#x20;     "https\://dev.hz.de",
&#x20;     "https\://web.purplemanager.com/heidenheimer-staging",
&#x20;     "resource://dynamic",
&#x20;   ],
&#x20; },
&#x20; targetUrlParams: \{},
&#x20; returnUrlParams: \{},
&#x20; removeUrlParams: \["jwt"],
&#x20; loginUrl: "https\://checkout-stage.hz.de/dispatch",
&#x20; logoutUrl: "https\://checkout-stage.hz.de/logout",
&#x20; checkoutUrl: "https\://checkout-stage.hz.de/start",
};
:::

## Configuration Reference

### Core Settings

type

**Type:** 'oAuth' | 'transferToken'
&#x20;**Description:** Specifies which SSO procedure is active.
&#x20;**Example:** "oAuth"

withPostMessage

**Type:** boolean
&#x20;**Description:** Enable triggering login/logout via postMessage for iframe integration.
&#x20;**Default:** false

### External State Configuration

The externalState object defines URL parameters that indicate authentication state from the SSO provider.

Structure

Each state is defined as an array: \[urlParameterKey, expectedValue?]

- If expectedValue is provided, the parameter value must match exactly
- If expectedValue is omitted, any value for that parameter is considered valid

externalState.login

**Type:** \[string, string?]
&#x20;**Description:** Indicates external login state
&#x20;**Example:** \["logged\_in", "true"] - Checks if URL has ?logged\_in=true

externalState.logout

**Type:** \[string, string?]
&#x20;**Description:** Indicates external logout state
&#x20;**Example:** \["logged\_in", "false"] - Checks if URL has ?logged\_in=false

externalState.purchase

**Type:** \[string, string?]
&#x20;**Description:** Indicates external purchase action completed
&#x20;**Example:** \["purchase", "true"] - Checks if URL has ?purchase=true

externalState.token

**Type:** \[string, string?]
&#x20;**Description:** TransferToken parameter, usually declared without expected value
&#x20;**Example:** \["transferToken"] - Checks if URL has ?transferToken=\<any\_value>

### URL Parameter Keys

Defines the URL parameter names used to trigger and handle authentication flows.

urlParamKeys.returnUrl

**Type:** string
&#x20;**Description:** URL parameter name under which the external API expects the return URL
&#x20;**Example:** "returnTo" - Results in ?returnTo=https\://yoursite.com

urlParamKeys.loginViaUrl

**Type:** string
&#x20;**Description:** URL parameter which triggers login. Can receive a string value to use as targetUrl
&#x20;**Example:** "external\_login" - Trigger with ?external\_login or ?external\_login=https\://custom-sso.com

urlParamKeys.checkoutViaUrl

**Type:** string
&#x20;**Description:** URL parameter which triggers checkout. Can receive a string value to use as targetUrl
&#x20;**Example:** "external\_checkout" - Trigger with ?external\_checkout or ?external\_checkout=https\://custom-checkout.com

urlParamKeys.logoutViaUrl

**Type:** string
&#x20;**Description:** URL parameter which triggers logout
&#x20;**Example:** "external\_logout" - Trigger with ?external\_logout

### PostMessage Configuration

postMessage.validOrigins

**Type:** string\[]
&#x20;**Description:** Array of origins allowed to trigger SSO Manager login/logout via postMessage
&#x20;**Security:** Only messages from these origins will be processed
&#x20;**Example:**

:::BlockQuote
validOrigins: \[
&#x20; "https\://dev.hz.de",
&#x20; "https\://web.purplemanager.com/heidenheimer-staging",
&#x20; "resource://dynamic"  // Non-standard protocol support
]
:::

### Additional URL Parameters

targetUrlParams

**Type:** Record\<string, string | number | boolean>
&#x20;**Description:** Additional parameters to add to the target SSO URL
&#x20;**Example:**

:::BlockQuote
targetUrlParams: \{
&#x20; client\_id: "your-client-id",
&#x20; response\_type: "token"
}
:::

returnUrlParams

**Type:** Record\<string, string | number | boolean>
&#x20;**Description:** Additional parameters to add to the return URL
&#x20;**Example:**

:::BlockQuote
returnUrlParams: \{
&#x20; source: "external\_auth",
&#x20; timestamp: Date.now()
}
:::

removeUrlParams

**Type:** string\[]
&#x20;**Description:** URL parameters to remove upon login/logout to avoid endless loops or expose sensitive data
&#x20;**Example:**

:::BlockQuote
removeUrlParams: \["jwt", "session\_id", "temp\_token"]
:::

### SSO Endpoint URLs

loginUrl

**Type:** string
&#x20;**Description:** URL to be called for external login when none is provided in the login call
&#x20;**Example:** "https\://checkout-stage.hz.de/dispatch"

logoutUrl

**Type:** string
&#x20;**Description:** URL to be called for logout (always used internally)
&#x20;**Example:** "https\://checkout-stage.hz.de/logout"

checkoutUrl

**Type:** string
&#x20;**Description:** URL to be called for external checkout when none is provided in the checkout call
&#x20;**Example:** "https\://checkout-stage.hz.de/start"

## Configuration Examples

### Basic OAuth Configuration

:::BlockQuote
export const config = \{
&#x20; type: "oAuth",
&#x20; withPostMessage: false,
&#x20; externalState: \{
&#x20;   login: \["logged\_in", "true"],
&#x20;   logout: \["logged\_in", "false"],
&#x20;   purchase: \["purchase", "true"],
&#x20;   token: \["transferToken"]
&#x20; },
&#x20; urlParamKeys: \{
&#x20;   returnUrl: "returnTo",
&#x20;   loginViaUrl: "external\_login",
&#x20;   checkoutViaUrl: "external\_checkout",
&#x20;   logoutViaUrl: "external\_logout",
&#x20; },
&#x20; loginUrl: "https\://sso.example.com/login",
&#x20; logoutUrl: "https\://sso.example.com/logout",
&#x20; checkoutUrl: "https\://sso.example.com/checkout",
};
:::

### Configuration with PostMessage Support

:::BlockQuote
export const config = \{
&#x20; type: "oAuth",
&#x20; withPostMessage: true,  // Enable postMessage
&#x20; externalState: \{
&#x20;   login: \["auth\_status", "authenticated"],
&#x20;   logout: \["auth\_status", "logged\_out"],
&#x20;   token: \["access\_token"]
&#x20; },
&#x20; urlParamKeys: \{
&#x20;   returnUrl: "callback",
&#x20;   loginViaUrl: "sso\_login",
&#x20;   checkoutViaUrl: "sso\_checkout",
&#x20;   logoutViaUrl: "sso\_logout",
&#x20; },
&#x20; postMessage: \{
&#x20;   validOrigins: \[
&#x20;     "https\://paywall.example.com",
&#x20;     "https\://iframe-app.example.com"
&#x20;   ]
&#x20; },
&#x20; targetUrlParams: \{
&#x20;   client\_id: "abc123",
&#x20;   scope: "read write"
&#x20; },
&#x20; returnUrlParams: \{
&#x20;   source: "sso\_return"
&#x20; },
&#x20; removeUrlParams: \["temp\_token", "session"],
&#x20; loginUrl: "https\://auth.example.com/oauth/login",
&#x20; logoutUrl: "https\://auth.example.com/oauth/logout",
&#x20; checkoutUrl: "https\://auth.example.com/subscribe",
};
:::

### Transfer Token Configuration

:::BlockQuote
export const config = \{
&#x20; type: "transferToken",
&#x20; withPostMessage: false,
&#x20; externalState: \{
&#x20;   login: \["status"],      // Any value accepted
&#x20;   logout: \["status"],     // Any value accepted
&#x20;   token: \["token"]        // Token key without specific value check
&#x20; },
&#x20; urlParamKeys: \{
&#x20;   returnUrl: "return\_url",
&#x20;   loginViaUrl: "auth\_login",
&#x20;   checkoutViaUrl: "auth\_checkout",
&#x20;   logoutViaUrl: "auth\_logout",
&#x20; },
&#x20; removeUrlParams: \["token", "status"],  // Clean sensitive data
&#x20; loginUrl: "https\://auth.example.com/token-login",
&#x20; logoutUrl: "https\://auth.example.com/token-logout",
&#x20; checkoutUrl: "https\://auth.example.com/token-checkout",
};
:::

## Usage Examples

### 1. Trigger Login via URL

**Basic login (uses default loginUrl):**

:::BlockQuote
https\://www\.hz.de?external\_login
:::

**Login with custom SSO URL:**

:::BlockQuote
https\://www\.hz.de?external\_login=https\://custom-sso.com/login
:::

**Login from a specific page:**

:::BlockQuote
https\://www\.hz.de/premium-article?external\_login
:::

After authentication, user returns to /premium-article.

### 2. Trigger Checkout via URL

**Basic checkout:**

:::BlockQuote
https\://www\.hz.de?external\_checkout
:::

**Checkout with custom URL:**

:::BlockQuote
https\://www\.hz.de?external\_checkout=https\://custom-checkout.com/subscribe
:::

### 3. Trigger Logout via URL

:::BlockQuote
https\://www\.hz.de?external\_logout
:::

### 4. Programmatic Login/Logout

:::BlockQuote
// Simple login
ssoManager.login();

// Login with custom URL
ssoManager.login('https\://custom-sso.com/login');

// Login with options
ssoManager.login('https\://sso.com/login', \{
&#x20; params: \[
&#x20;   \['promo', 'summer2024'],
&#x20;   \['source', 'email']
&#x20; ],
&#x20; returnUrl: 'https\://www\.hz.de/thank-you'
});

// Checkout
ssoManager.checkout();
ssoManager.checkout('https\://custom-checkout.com');

// Logout
ssoManager.logout();
:::

### 5. PostMessage Integration (for iframes)

Enable in config:

:::BlockQuote
withPostMessage: true,
postMessage: \{
&#x20; validOrigins: \[
&#x20;   "https\://paywall-iframe.com"
&#x20; ]
}
:::

From iframe:

:::BlockQuote
// Trigger login
window\.parent.postMessage(\{
&#x20; method: 'login',
&#x20; targetUrl: 'https\://sso.com/login' // optional
}, '\*');

// Trigger checkout
window\.parent.postMessage(\{
&#x20; method: 'checkout',
&#x20; targetUrl: 'https\://checkout.com/start' // optional
}, '\*');

// Trigger logout
window\.parent.postMessage(\{
&#x20; method: 'logout'
}, '\*');
:::

## Authentication Flow

### Web Platform Flow

:::BlockQuote
1\. User clicks login link
&#x20;  └─> https\://www\.hz.de/article?external\_login

2\. Script redirects to SSO
&#x20;  └─> https\://sso.example.com/login?returnTo=https\://www\.hz.de/article

3\. User authenticates on SSO provider

4\. SSO redirects back with auth params
&#x20;  └─> https\://www\.hz.de/article?transferToken=abc123\&logged\_in=true

5\. Script processes authentication
&#x20;  ├─> Validates token parameter
&#x20;  ├─> Calls purple.entitlement.login()
&#x20;  └─> Cleans URL: https\://www\.hz.de/article

6\. Page reloads with user logged in
:::

### App Platform Flow

:::BlockQuote
1\. User triggers login from app

2\. Script calls purple.app.performAuthentication()
&#x20;  └─> Native app handles OAuth in system browser

3\. App receives auth callback

4\. Script processes returned data
&#x20;  ├─> Validates authentication state
&#x20;  └─> Calls purple.entitlement.login()

5\. App refreshes with user logged in
:::

## Advanced Configuration

### Singleton Pattern

The SSOManager class uses a built-in singleton pattern. When you extend this class, the singleton behavior is automatically inherited:

:::BlockQuote
// All of these return the SAME instance
const instance1 = new SSOManager();
const instance2 = new SSOManager();
const instance3 = SSOManager.getInstance();

console.log(instance1 === instance2 === instance3); // true
:::

When you extend the class in init.js, the extended class automatically inherits the singleton pattern:

:::BlockQuote
class ExtendedSSOManager extends SSOManager \{
&#x20; // Your custom implementation
}

const ext1 = new ExtendedSSOManager();
const ext2 = new ExtendedSSOManager();
console.log(ext1 === ext2); // true - singleton works!
:::

**For Testing:**

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';

// Reset the singleton between tests
beforeEach(() => \{
&#x20; SSOManager.resetInstance();
});

it('should initialize properly', () => \{
&#x20; const instance = new SSOManager();
&#x20; expect(instance).toBeDefined();
});
:::

### Custom Lifecycle Hooks

Extend the class in init.js to add custom behavior. The singleton pattern is automatically inherited - no extra work needed:

**File: default/storefront/assets/scripts/custom/sso-manager/init.js**

:::BlockQuote
import \{ SSOManager } from './sso-manager';
import \{ extendStorefrontHook } from "../../utils/extend-storefront-hook";

class CustomSSOManager extends SSOManager \{
&#x20; onLogin = \{
&#x20;   app: async () => \{
&#x20;     // Track login analytics
&#x20;     analytics.track('user\_logged\_in', \{ platform: 'app' });
&#x20;    &#x20;
&#x20;     // Clear local cache
&#x20;     await clearCache();
&#x20;   },
&#x20;   web: async () => \{
&#x20;     // Track login analytics
&#x20;     analytics.track('user\_logged\_in', \{ platform: 'web' });
&#x20;    &#x20;
&#x20;     // Update UI
&#x20;     updateUserInterface();
&#x20;   }
&#x20; }

&#x20; onLogout = \{
&#x20;   app: async () => \{
&#x20;     // Clear user data
&#x20;     await clearUserData();
&#x20;   },
&#x20;   web: async () => \{
&#x20;     // Reset session
&#x20;     sessionStorage.clear();
&#x20;   }
&#x20; }
}

// Automatic initialization via hook
export let ssoManager = null;

extendStorefrontHook('onPurpleServiceInit', () => \{
&#x20; if (!ssoManager) \{
&#x20;   ssoManager = new CustomSSOManager();
&#x20; }
});
:::

**That's it!** The singleton pattern is inherited from the parent class, and initialization happens automatically when Purple Service initializes.

### Adding Parameters to Target URL

:::BlockQuote
targetUrlParams: \{
&#x20; client\_id: "your-client-id",
&#x20; response\_type: "token",
&#x20; scope: "read write"
}
:::

Result: SSO URL will include these params automatically.

### Adding Parameters to Return URL

:::BlockQuote
returnUrlParams: \{
&#x20; source: "external\_auth",
&#x20; timestamp: Date.now()
}
:::

Result: Return URL will include these params.

### Removing Sensitive Parameters

:::BlockQuote
removeUrlParams: \[
&#x20; "jwt",
&#x20; "session\_id",
&#x20; "temp\_token"
]
:::

These parameters are removed after authentication to keep URLs clean.

## API Reference

### Singleton Methods

SSOManager.getInstance()

Get the singleton instance. Creates a new instance if none exists.

**Returns**: SSOManager - The singleton instance

**Example:**

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';

const auth = SSOManager.getInstance();
auth.login();
:::

SSOManager.resetInstance()

Reset the singleton instance. Useful for testing or reinitialization.

**Returns**: void

**Example:**

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';

// For testing purposes
SSOManager.resetInstance();
const newInstance = new SSOManager();
:::

### Initialization Functions

The SSO Manager uses automatic initialization via Purple Service hooks:

:::BlockQuote
// In custom.js - import to activate
import './sso-manager/init.js';

// In init.js - automatic initialization
import \{ ssoManager } from './sso-manager/init.js';

extendStorefrontHook('onPurpleServiceInit', () => \{
&#x20;   if (!ssoManager) \{
&#x20;       ssoManager = new ExtendedSSOManager();
&#x20;   }
});
:::

**No manual initialization required** - the singleton is created automatically when Purple Service initializes.

Direct Access

If you need to access the singleton directly without waiting for the hook:

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';
const instance = SSOManager.getInstance();
:::

### Public Methods

login(targetUrl?, options?)

Trigger the login flow.

**Parameters:**

- targetUrl (string, optional): SSO login URL. Uses config.loginUrl if omitted.
- options (object, optional):&#x20;
  - params (array): Additional URL parameters as \[key, value] pairs
  - returnUrl (string): Custom return URL for this login

**Example:**

:::BlockQuote
ssoManager.login('https\://sso.com/login', \{
&#x20; params: \[\['promo', 'welcome']],
&#x20; returnUrl: 'https\://www\.hz.de/success'
});
:::

checkout(targetUrl?, options?)

Trigger the checkout flow. Same signature as login().

**Example:**

:::BlockQuote
ssoManager.checkout('https\://checkout.com/start');
:::

logout()

Trigger the logout flow.

**Example:**

:::BlockQuote
ssoManager.logout();
:::

### Public Properties

config

Access the configuration object.

:::BlockQuote
console.log(ssoManager.config.loginUrl);
ssoManager.config.loginUrl = 'https\://new-sso.com/login';
:::

isWeb

Boolean indicating if running on web platform.

:::BlockQuote
if (ssoManager.isWeb) \{
&#x20; console.log('Running on web');
}
:::

utils

Utility functions for URL handling.

:::BlockQuote
// Clear URL parameters
ssoManager.utils.clearUrlParams();

// Get return URL
const returnUrl = ssoManager.utils.getReturnUrl();

// Get stripped URL (without auth params)
const cleanUrl = ssoManager.utils.getStrippedUrl();
:::

## Troubleshooting

### Singleton-Related Issues

**Problem**: Configuration changes don't take effect.

**Solution**: The singleton instance is created with the initial configuration. To change configuration:

:::BlockQuote
import \{ SSOManager } from './sso-manager/sso-manager.js';

// Option 1: Reset and recreate
SSOManager.resetInstance();
const newInstance = new SSOManager();

// Option 2: Modify existing instance config (runtime changes)
const instance = SSOManager.getInstance();
instance.config.loginUrl = 'https\://new-url.com';
:::

**Problem**: Different parts of the app have different authentication states.

**Solution**: This shouldn't happen with the singleton pattern. If it does, verify that:

1. You're not accidentally calling resetInstance() somewhere
2. All parts of your app import from the same module path
3. No bundler/module issues are creating duplicate instances

All instantiations should go through the same singleton.

### Login Not Triggered

**Problem**: Clicking login link doesn't redirect to SSO.

**Solutions**:

1. Check if loginUrl is set in config
2. Verify URL parameter matches loginViaUrl in config
3. Check browser console for errors

### Infinite Redirect Loop

**Problem**: Page keeps redirecting to SSO and back.

**Solutions**:

1. Verify externalState configuration matches SSO parameters
2. Add problematic parameters to removeUrlParams array
3. Check that SSO is sending expected parameter names/values

### PostMessage Not Working

**Problem**: iframe postMessage doesn't trigger login.

**Solutions**:

1. Enable postMessage: withPostMessage: true
2. Add iframe origin to validOrigins array
3. Check browser console for origin validation errors
4. For non-standard protocols (resource://), ensure URL format is correct

### Return URL Issues

**Problem**: User returns to wrong page after authentication.

**Solutions**:

1. Check returnUrl parameter name matches SSO expectation
2. Verify urlParamKeys.returnUrl is configured correctly
3. Use custom return URL in login options if needed

### App Authentication Fails

**Problem**: Authentication doesn't work in native app.

**Solutions**:

1. Verify purple.app.performAuthentication is available
2. Check callbackParamName matches app configuration
3. Ensure app has proper URL scheme registration

## Security Considerations

### PostMessage Origin Validation

Always specify exact origins in validOrigins:

:::BlockQuote
postMessage: \{
&#x20; validOrigins: \[
&#x20;   "https\://trusted-iframe.com",
&#x20;   "https\://paywall.example.com"
&#x20; ]
}
:::

**Never use wildcards** in production!

### Token Handling

- Tokens are passed via URL parameters and immediately processed
- URLs are cleaned after token extraction using removeUrlParams
- Tokens should be short-lived (\< 5 minutes)

### HTTPS Only

Always use HTTPS URLs for SSO endpoints in production:

:::BlockQuote
loginUrl: "https\://sso.example.com/login"  // ✅ Good
loginUrl: "http\://sso.example.com/login"   // ❌ Bad
:::

## Browser Support

- **Modern Browsers**: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
- **Mobile**: iOS Safari 14+, Chrome Mobile 90+
- **Required APIs**:&#x20;
  - URL / URLSearchParams
  - window\.history.replaceState
  - window\.addEventListener (postMessage)
  - Promises / async-await

## Support

For issues or questions:

1. Check console logs (debug level)
2. Verify configuration matches SSO provider requirements
3. Review authentication flow in Network tab
4. Contact your SSO provider for server-side issues
