Browser SDK for websites that use Schibsted Account for login, session checks, user identifiers, the simplified login widget, and access checks.
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',
env: 'PRE',
defaultProductIds: ['product-id'],
});
Required configuration:
clientId: your Schibsted Account client idredirectUri: the callback URL registered for your clientsessionDomain: your client-configured Session Service domainOptional configuration:
env: defaults to PRE; supported keys include LOCAL, DEV, PRE, PRO, PRO_NO,
PRO_FI, and PRO_DKcallbackBeforeRedirect: called before a full-page session refresh redirectdefaultProductIds: product or feature ids used when hasAccess() is called without argumentslog: receives SDK debug log linesvarnish: enables Varnish cookie handlingwindow: overrides the browser window object, mostly useful in testsThe 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 Initialized SDK" section below).
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.
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:
- 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.
- 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.
// 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, AccountV5>;
// 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({ state: createLoginState() });
});
}
hasSession()hasAccess()on() and off()Use these methods to inspect the current Session Service state:
account.hasSession() returns the raw SDK-known session responseaccount.isLoggedIn() returns whether the browser has a Schibsted Account sessionaccount.isConnected() returns whether the user is connected to your clientaccount.getUser() returns the connected user session dataaccount.getUserId() returns the realm-specific user id as a numberaccount.getExternalId() returns an identifier specific to the configured client and supplied
external party; this method is deprecated and retained only for compatibility with existing
integrationsaccount.getUserUuid() returns the globally unique user idaccount.getUserSDRN() returns the user SDRNaccount.getSpId() returns the Varnish sp_id value when presentSome 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,
callbackBeforeRedirect: () => {
saveCurrentUiState();
},
});
Use account.login() to start the OAuth authorization flow:
account.login({
state: createLoginState(),
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({ state: createLoginState() });
statestate 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:
account.login({ state }) with that token, or with an encoded payload that
contains it.redirectUri with code and state.state before exchanging code for tokens.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({
state: createLoginState(),
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 (!(await account.isConnected()) && shouldShowSimplifiedLogin()) {
const opened = await account.showSimplifiedLoginWidget(
{ state: createLoginState },
{ locale: 'nb' },
);
if (opened) {
rememberSimplifiedLoginWasShown();
}
}
state may be a string or a function that returns a string or Promise<string>. The function is
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 urlsessionUpdated — a CustomEvent emitted when hasSession() detects a change in the stored
session state; detail contains the new session responsesimplifiedLoginOpened — an Event emitted when the widget is displayedsimplifiedLoginCancelled — an Event emitted when the widget is closedThe 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());
}
}
For setup requirements, integration flow, and project scripts, see the project README.