@schibsted/account-sdk-browser
    Preparing search index...

    @schibsted/account-sdk-browser

    Schibsted Account SDK for browsers

    Browser SDK for websites that use Schibsted Account for login, session checks, user identifiers, the simplified login widget, and access checks.

    Upgrading from v5? See the v6 Migration Guide.

    Create a singleton SDK instance with createAccountSdk:

    import { createAccountSdk } from '@schibsted/account-sdk-browser';

    const account = createAccountSdk({
    clientId: '56e9a5d1eee0000000000000',
    redirectUri: 'https://awesomenews.site/callback',
    sessionDomain: 'https://id.awesomenews.site',
    generateState: () => createLoginState(),
    env: 'PRE',
    defaultProductIds: ['product-id'],
    });

    Required configuration:

    • clientId: your Schibsted Account client id
    • redirectUri: the callback URL registered for your client
    • sessionDomain: your client-configured Session Service domain
    • generateState: called with the configured window to create the OAuth state whenever login(), loginUrl(), or the simplified login widget start a login without an explicit state (see "Handling state" below)

    Optional configuration:

    • env: defaults to PRE; supported keys include LOCAL, DEV, PRE, PRO, PRO_NO, PRO_FI, and PRO_DK
    • callbackBeforeRedirect: called before a full-page session refresh redirect
    • defaultProductIds: product or feature ids used when hasAccess() is called without arguments
    • log: receives SDK debug log lines
    • varnish: enables Varnish cookie handling
    • window: overrides the browser window object, mostly useful in tests

    The configured Account.clientId, Account.env (with the PRE default applied), Account.redirectUri, and Account.defaultProductIds (a frozen copy) are available as read-only properties after creation.

    const { clientId, env, redirectUri, defaultProductIds } = account;
    

    createAccountSdk() registers the instance on window.schAccount and emits schAccount:ready event, but these should normally not be used directly (see Using the SDK Instance).

    window.addEventListener('schAccount:ready', (event) => {
    const account = event.detail.instance;
    });

    Use getAccountSdk when another module needs to wait for the initialized Account

    import { getAccountSdk } from '@schibsted/account-sdk-browser';

    const account = await getAccountSdk();

    getAccountSdk() resolves immediately if the SDK has already been initialized. Otherwise it waits for schAccount:ready and rejects if initialization does not happen within 4000 ms.

    Tip

    Cross-package consumers should depend on the smallest interface they need, rather than on Account interface itself. Dependency injection provides the mechanism for passing the getAccountSdk implementation across the package boundary. Together with TypeScript’s structural typing system, this allows for flexible and decoupled code. This has two major benefits:

    1. Packages do not need to use the same Account SDK version, as long as the Account SDK implementation satisfies the interface required by the consuming package.
    2. The interface can be fulfilled by another provider, for example a different identity provider or a test double.

    It is possible for the consuming package to import getAccountSdk directly. However, this creates a direct dependency on the Account SDK and couples the package to that specific implementation. Dependency injection is therefore recommended for cross-package integrations in most cases, see example below.

    Dependency Injection Example Example:
    // my-module.ts

    // Declare the subset of the Account API that this module requires
    export interface AccountInterface {
    getUserId: () => Promise<number>; // Note: not compatible with the SDK v5 version which might also return a string
    }
    // Declare the Provider interface, compatible with the `getAccountSdk` function
    export type AccountProvider = () => Promise<AccountInterface>;

    class SomeModule {
    private accountProvider: AccountProvider;

    constructor(accountProvider: AccountProvider) {
    this.accountProvider = accountProvider;
    }

    async getUserId(): Promise<number> {
    const account = await this.accountProvider();
    return account.getUserId();
    }
    }
    // my-module.test-types.ts
    // Utilize some type checks to ensure AccountInterface is implemented by the latest Account SDK interface
    import type { Account as AccountV6 } from '@schibsted/account-sdk-browser-v6'; // dev dependency latest

    import type { Identity as IdentityV5 } from '@schibsted/account-sdk-browser-v5'; // dev dependency previous

    type AssertImplements<TRequired, TActual extends TRequired> = true;
    type _AssertLatestAccountSdkIsCompatible = AssertImplements<AccountInterface, AccountV6>;
    // NOTE: This check will make the compiler catch an error for us! 🎉
    // This will fail compilation, because the getUserId method in AccountInterface returns a number, while the SDK v5 version might return a string.
    type _AssertPreviousAccountSdkIsCompatible = AssertImplements<AccountInterface, IdentityV5>;
    // my-app/main.ts
    import { getAccountSdk } from '@schibsted/account-sdk-browser';
    import { SomeModule } from '@schibsted/some-module';

    const someModule = new SomeModule(getAccountSdk);

    After initialization, use the Account instance to render user state and start login when needed:

    async function renderLoginState() {
    const container = document.getElementById('login-container');

    if (await account.isConnected()) {
    const user = await account.getUser();
    container.textContent = `Hello ${user.givenName || user.displayName || user.userId}`;
    return;
    }

    container.innerHTML = '<button type="button">Log in</button>';
    container.querySelector('button').addEventListener('click', () => {
    account.login();
    });
    }

    Use these methods to inspect the current Session Service state:

    • account.hasSession() returns the raw SDK-known session response
    • account.isLoggedIn() returns whether the browser has a Schibsted Account session
    • account.isConnected() returns whether the user is connected to your client
    • account.getUser() returns the connected user session data
    • account.getUserId() returns the realm-specific user id as a number
    • account.getExternalId() returns an identifier specific to the configured client and supplied external party; this method is deprecated and retained only for compatibility with existing integrations
    • account.getUserUuid() returns the globally unique user id
    • account.getUserSDRN() returns the user SDRN
    • account.getSpId() returns the Varnish sp_id value when present

    Some session methods can trigger a full-page Session Service refresh redirect, especially for Safari-based browsers. Use callbackBeforeRedirect if you need to persist client state before that happens.

    const account = createAccountSdk({
    clientId,
    redirectUri,
    sessionDomain,
    generateState: () => createLoginState(),
    callbackBeforeRedirect: () => {
    saveCurrentUiState();
    },
    });

    Use account.login() to start the OAuth authorization flow:

    account.login({
    scope: 'openid',
    preferPopup: false,
    });

    By default the SDK redirects the current window. Set preferPopup: true when login is triggered by a user gesture and you want to try a popup first. If the popup cannot be opened, the SDK falls back to the redirect flow.

    If you only need the URL, use account.loginUrl():

    const url = account.loginUrl();
    

    The SDK forwards the user's current consents to Schibsted account in every login URL. The consents are read by a getConsents reader, which returns them serialized as URLSearchParams; the SDK appends them to the login URL as they are. The reader is called synchronously every time a login URL is created, so it must return the current state immediately instead of waiting for the consent dialog. login() and loginUrl() stay synchronous; a login started before the user answers the dialog (e.g. from the dialog itself) continues without consents.

    Login links rendered before the user answered the consent dialog are refreshed when they are clicked: the SDK listens to clicks on the document and updates the consents of <a> elements that point to its own login URL. Links inside iframes and URLs opened with window.open() are not refreshed; create the URL at the time of the user's action if that matters.

    By default, consents are read from the Schibsted Sourcepoint integration (window._tcf_ or window._scc_), so Schibsted sites forward consents out of the box, without any configuration:

    import { createAccountSdk } from '@schibsted/account-sdk-browser';

    const account = createAccountSdk({
    clientId: '56e9a5d1eee0000000000000',
    redirectUri: 'https://awesomenews.site',
    sessionDomain: 'https://id.awesomenews.site',
    generateState: () => createLoginState(),
    // Optional: opt out of refreshing rendered login links
    refreshLoginLinks: false,
    });

    Sites without the Schibsted Sourcepoint integration pass their own reader:

    import { createAccountSdk } from '@schibsted/account-sdk-browser';

    const account = createAccountSdk({
    clientId: '56e9a5d1eee0000000000000',
    redirectUri: 'https://awesomenews.site',
    sessionDomain: 'https://id.awesomenews.site',
    generateState: () => createLoginState(),
    // Reads the decision of your CMP, see the interface below
    getConsents: readConsentsFromMyCmp,
    });

    Use getConsents: () => null to forward no consents.

    type ConsentReader = () => URLSearchParams | null;
    

    A reader must:

    • Be synchronous. Return the decision the CMP already has, e.g. from its in-memory state or its stored consent cookie. Do not return a Promise or wait for the consent dialog.
    • Return URLSearchParams or null. null or empty params mean the decision is unknown, and no consent parameters are added. The SDK logs any other return value and any thrown error through log (or console.warn if no callback is configured), and the login continues without consents.
    • Return only consent parameters. Every key it returns is added to the login URL. When a rendered link is refreshed, those keys replace the values already on it, so do not return OAuth parameters such as state or redirect_uri. Repeated keys (e.g. purpose=a&purpose=b) are kept.

    Return these parameters (format v1, the only supported version):

    Parameter Value
    consents Alphabetically sorted, comma-separated purposes the user granted, or rejected if the user rejected all of them. Purposes: advertising, analytics, marketing, personalisation
    consent_version Version of this parameter format; always v1

    Examples of serialized params returned by a reader:

    User's decision Returned URLSearchParams (serialized)
    Granted advertising and analytics consents=advertising%2Canalytics&consent_version=v1
    Granted analytics only consents=analytics&consent_version=v1
    Rejected all purposes consents=rejected&consent_version=v1
    Not answered yet null (or empty URLSearchParams)

    state is an opaque OpenID Connect value that is returned to your redirectUri with the authorization code. Use it to protect against CSRF and to restore application state.

    A common pattern is:

    1. The generateState function passed to createAccountSdk() creates a random, unique CSRF token, persists it on the device (e.g. in a cookie) together with any application state you want to restore, and returns the token. The SDK calls it with the configured window each time a login starts.
    2. Schibsted Account redirects back to your redirectUri with code and state.
    3. Your backend compares the returned state with the persisted token before exchanging code for tokens, then uses the persisted application state (e.g. returnTo) to redirect the user.
    const generateState = (window) => {
    const csrf = getCsrfToken();
    const stateData = encodeURIComponent(JSON.stringify({ csrf, returnTo: window.location.href }));
    window.document.cookie = `login-state=${stateData}; path=/; secure; samesite=lax`;
    return csrf;
    };

    const account = createAccountSdk({
    clientId,
    redirectUri,
    sessionDomain,
    generateState,
    });

    A cookie is used rather than localStorage/sessionStorage because the browser sends it with the redirect to redirectUri, so your backend can validate state directly.

    generateState must synchronously return a non-empty string. To override it for a single login, pass state explicitly, e.g. account.login({ state: 'custom-state' }).

    Keep Access Tokens and Refresh Tokens on your backend. Do not send them to the browser.

    Use acrValues when you need to request a specific authentication method:

    account.login({
    acrValues: 'otp-email',
    });

    Supported values include password, otp, sms, otp-email, eid, eid-no, eid-se, eid-fi, and eid-dk. Values other than otp-email can be combined as a space-separated string where supported by the account flow. Verify the resulting AMR claim in the ID token on your backend if you need to enforce a completed authentication method.

    Use account.logout() to remove the brand Session Service session and redirect the browser:

    account.logout('https://awesomenews.site/logged-out');
    

    Use account.logoutUrl() when you need the logout URL without navigating immediately:

    const logoutUrl = account.logoutUrl('https://awesomenews.site/logged-out');
    

    Use account.accountUrl() to send the user to their Schibsted Account profile page:

    window.location.href = account.accountUrl('https://awesomenews.site/account-return');
    

    Use account.hasAccess() to check access to Schibsted Account product ids or Zuora feature ids:

    const access = await account.hasAccess();

    if (access?.entitled) {
    showPremiumContent();
    } else {
    showPaywall();
    }

    Pass product ids to override the configured defaults for a specific check:

    const access = await account.hasAccess(['other-product-id']);
    

    hasAccess() requires sessionDomain. Results are cached according to the Session Service TTL. If no product ids are passed to the method, defaultProductIds must be configured. Use account.clearCachedAccessResult(productIds, userId) when you need to invalidate that cache.

    Before using the simplified login widget, make sure your site has no site-specific terms and conditions in the Schibsted Account login flow.

    The widget is shown only when the SDK can read enough user context from the global account session. Your application decides when and how often to show it. If that context is unavailable (for example, the user has no SSO session or third-party cookies are blocked), the method rejects with SDKError instead of returning false. Handle the rejection and keep the regular login flow available.

    if (!(await account.isConnected()) && shouldShowSimplifiedLogin()) {
    try {
    const opened = await account.showSimplifiedLoginWidget({}, { locale: 'nb' });

    if (opened) {
    rememberSimplifiedLoginWasShown();
    }
    } catch (error) {
    console.debug('Simplified login widget unavailable', error);
    }
    }

    By default the widget uses the configured generateState. To override it, pass state as a string or a function that returns a string or Promise<string>. Both are called only if the user continues from the widget.

    The SDK exposes fluent Account#on and Account#off aliases for browser-native event listeners. The loginRequested, logoutRequested, and sessionUpdated events are CustomEvent instances and provide their data in event.detail:

    import type { AccountEventListener } from '@schibsted/account-sdk-browser';

    const handleLoginRequested: AccountEventListener<'loginRequested'> = (event) => {
    console.log('Login started', event.detail.url, event.detail.method);
    };

    account.on('loginRequested', handleLoginRequested);
    account.off('loginRequested', handleLoginRequested);

    account.on('sessionUpdated', (event) => {
    console.log('Session data updated', event.detail);
    });

    Available Account events:

    • loginRequested — a CustomEvent whose detail contains url and the method (popup or default)
    • logoutRequested — a CustomEvent whose detail contains the logout url
    • sessionUpdated — a CustomEvent emitted when hasSession() detects a change in userId or result; detail contains the new session response. Changes to other fields alone do not emit this event.
    • simplifiedLoginOpened — an Event emitted when the widget is displayed
    • simplifiedLoginCancelled — an Event emitted when the widget is closed

    The SDK also emits schAccount:ready on window when an Account instance is created.

    Validation, network, and service failures from API calls reject or throw SDKError. Convenience methods such as isLoggedIn(), isConnected(), and getSpId() return fallback values instead of throwing when the session lookup fails.

    import { SDKError } from '@schibsted/account-sdk-browser';

    try {
    await account.getUser();
    } catch (error) {
    if (error instanceof SDKError) {
    console.error(error.toString());
    }
    }