Bonnier News OIDC Connector
Express middleware for connecting to the Bonnier News OpenID Connect Provider, providing authentication and authorization functionality for Node.js applications.
Table of Contents
Installation
npm install @bonniernews/bn-oidc-connector
Quick Start
import { auth, isAuthenticated, isEntitled } from "@bonniernews/bn-oidc-connector";
import express from "express";
const app = express();
app.use(auth({
clientId: "your-client-id",
issuerBaseURL: new URL("https://bn-login-id-service-lab.bnu.bn.nr"),
baseURL: new URL("https://your-app-domain.com"),
scopes: [ "profile", "email", "entitlements", "offline_access" ]
}));
app.get("/profile", isAuthenticated, (req, res) => {
res.json({ user: req.oidc.user });
});
app.get("/admin", isEntitled([ "admin" ]), (req, res) => {
res.json({ message: "Admin area" });
});
app.listen(3000);
Configuration
AuthOptions
clientId | string | âś… | - | Your OIDC client ID |
clientSecret | string | ❌ | - | Your OIDC client secret (for confidential clients) |
issuerBaseURL | URL | âś… | - | Base URL of the OIDC provider |
baseURL | URL | âś… | - | Base URL of your application |
loginPath | string | ❌ | "/id/login" | Path for login endpoint |
logoutPath | string | ❌ | "/id/logout" | Path for logout endpoint |
loginCallbackPath | string | ❌ | "/id/login/callback" | Path for login callback endpoint |
logoutCallbackPath | string | ❌ | "/id/logout/callback" | Path for logout callback endpoint |
scopes | string[] | ❌ | ["openid", "entitlements", "offline_access"] | OAuth scopes to request |
prompts | string[] | ❌ | [] | Custom prompts for the authorization request |
afterLoginCallback | function | ❌ | - | Custom handler after successful login |
afterLogoutCallback | function | ❌ | - | Custom handler after successful logout |
cookies | object | ❌ | See below | Cookie configuration |
Cookie Configuration
The connector uses several cookies to manage authentication state and tokens. The default cookie names are:
cookies: {
authParams: "bnoidcap",
tokens: {
access: "bnoidcat",
refresh: "bnoidcrt",
id: "bnoidcit",
expiresIn: "bnoidcei"
},
logout: "bnoidclo"
}
Scopes Configuration
The scopes option determines what information and permissions your application requests from the OIDC provider. Scopes control access to user data and functionality.
scopes: [ "openid", "profile", "email", "entitlements", "offline_access" ]
Available Scopes:
"openid" | Basic OpenID Connect authentication | User ID (sub claim) |
"profile" | Access to user's profile information | Name (given_name and family_name claims) |
"email" | Access to user's email address | Email address and verification status (email and email_verified claims) |
"entitlements" | Access to user's entitlements | User entitlements (ent claim) |
"external_ids" | Access to user's external IDs | External IDs for Didomi and Google Ads (external_ids claim) |
"offline_access" | Request refresh token for long-term access | Enables token refresh without re-authentication |
Important Notes:
- You do not need to specify
"openid" in your configuration; it will be included automatically
"entitlements" is needed if you plan to use isEntitled() middleware
"offline_access" is highly recommended for applications to enable automatic token refresh
Prompts Configuration
The prompts option allows you to customize the authentication behavior by specifying OpenID Connect prompt parameters that control how the authorization server interacts with the user.
prompts: [ "login", "consent" ]
Available Prompts:
"none" | No user interaction. If authentication/consent is required, returns an error instead of prompting the user |
"login" | Forces the user to re-authenticate, even if they have an active session |
"consent" | Prompts the user to provide consent for the application's requested scopes again (note: this is currently not in use) |
"select_account" | Forces the user to confirm or switch accounts if already signed in. Note: This is currently the only way to log in as a different user if you are already authenticated without requiring a full logout from the provider. |
Dynamic Prompts:
You can also specify prompts dynamically during login:
app.get("/secure-login", (req, res) => {
res.oidc.login(req, res, {
prompts: [ "login" ],
returnTo: "/sensitive-area"
});
});
Middleware
The library provides several middleware functions for different authentication and authorization scenarios.
Core Middleware
auth(options: AuthOptions)
The main middleware that initializes the OIDC connector and sets up authentication routes. It should be registered as early as possible in your Express app.
import { auth } from "@bonniernews/bn-oidc-connector";
app.use(auth({
clientId: "your-client-id",
issuerBaseURL: new URL("https://bn-login-id-service-lab.bnu.bn.nr"),
baseURL: new URL("https://your-app.com")
}));
Features:
- Initializes OIDC configuration and JWKS
- Sets up authentication routes
- Adds OIDC context to request/response objects
- Handles automatic token refresh
- Processes query parameters for authentication flows
Routes Created:
GET /id/login - Initiates login flow
GET /id/logout - Initiates logout flow
GET /id/login/callback - Handles login callback
GET /id/logout/callback - Handles logout callback
Guard Middleware
The library provides two guard middleware functions for protecting routes:
isAuthenticated
Ensures the user is authenticated before accessing a route.
import { isAuthenticated } from "@bonniernews/bn-oidc-connector";
app.get("/protected", isAuthenticated, (req, res) => {
res.json({ message: "Hello authenticated user!" });
});
Behavior:
- Checks
req.oidc.isAuthenticated
- Throws
UnauthenticatedError if user is not authenticated
- Allows request to continue if user is authenticated
Use when: You only need to verify that the user is logged in, without checking specific permissions.
isEntitled(validEntitlements: string[])
Checks if the authenticated user has specific entitlements. Note: This middleware automatically checks for authentication first, so isAuthenticated is not needed when using isEntitled.
import { isEntitled } from "@bonniernews/bn-oidc-connector";
app.get("/admin", isEntitled([ "admin", "super-user" ]), (req, res) => {
res.json({ message: "Admin access granted" });
});
Parameters:
validEntitlements: Array of entitlement strings. User needs at least one.
Behavior:
- First checks if user is authenticated (throws
UnauthenticatedError if not)
- Returns
true if validEntitlements is empty
- Checks user's entitlements from ID token claims (
ent field)
- Throws
UnauthorizedError if user lacks required entitlements
Use when: You need to verify both authentication and specific permissions/roles.
When to Use Which Middleware
app.get("/profile", isAuthenticated, (req, res) => { ... });
app.get("/admin", isEntitled([ "admin" ]), (req, res) => { ... });
app.get("/admin", isAuthenticated, isEntitled([ "admin" ]), (req, res) => { ... });
app.get("/dynamic", isAuthenticated, (req, res) => {
const hasAccess = req.oidc.isEntitled([ "premium" ]);
});
Request Context
The middleware adds an oidc property to the Express Request object with the following structure:
req.oidc: OidcRequestContext
config | OidcConfig | Complete OIDC configuration including client config, well-known config, and signing keys |
accessToken | string | undefined | Current access token |
refreshToken | string | undefined | Current refresh token |
idToken | string | undefined | Current ID token (JWT) |
expiresIn | number | undefined | Token expiration time in seconds |
idTokenClaims | Record<string, any> | undefined | Decoded ID token claims |
isAuthenticated | boolean | Whether the user is currently authenticated |
user | object | undefined | User information extracted from ID token |
isEntitled | function | Function to check user entitlements |
User Object
req.oidc.user: {
id: string;
email?: string;
}
isEntitled Function
req.oidc.isEntitled(validEntitlements: string[]): boolean
Check if the current user has any of the specified entitlements.
Example:
app.get("/content", isAuthenticated, (req, res) => {
if (req.oidc.isEntitled([ "premium", "subscriber" ])) {
res.json({ content: "Premium content" });
} else {
res.json({ content: "Free content" });
}
});
Response Context
The middleware adds an oidc property to the Express Response object with authentication flow methods:
res.oidc: OidcResponseContext
login | (req, res, options?) => void | Initiates login flow |
loginCallback | (req, res) => void | Handles login callback |
logout | (req, res, options?) => void | Initiates logout flow |
logoutCallback | (req, res) => void | Handles logout callback |
refresh | (req, res) => Promise<void> | Refreshes tokens |
Login Options
type LoginOptions = {
returnTo?: string;
scopes?: string[];
prompts?: string[];
locale?: string;
}
Logout Options
type LogoutOptions = {
returnTo?: string;
}
Manual Authentication Flow
app.get("/custom-login", (req, res) => {
res.oidc.login(req, res, {
returnTo: "/dashboard",
scopes: [ "profile", "email" ],
locale: "sv-SE"
});
});
app.get("/custom-logout", (req, res) => {
res.oidc.logout(req, res, {
returnTo: "/goodbye"
});
});
API Reference
Query Parameters
The middleware automatically handles special query parameters:
idlogin
Triggers automatic login flow:
GET /some-page?idlogin=true
GET /some-page?idlogin=silent // Uses prompt=none for silent authentication
idrefresh
Triggers token refresh:
GET /some-page?idrefresh=true
This is useful in scenarios where claims or entitlements may have changed during the user's session—such as after a purchase, subscription upgrade, or profile update—allowing the application to refresh tokens and retrieve updated user information without requiring the user to log in again.
Authentication Flow
- Login: User visits
/id/login or a page with ?idlogin=true
- Authorization: User is redirected to OIDC provider
- Callback: Provider redirects to
/id/login/callback with authorization code
- Token Exchange: Authorization code is exchanged for tokens
- Session: Tokens are stored in secure cookies
- Access: User can access protected resources
Token Management
- Access tokens are used for API authentication
- Refresh tokens are used to obtain new access tokens
- ID tokens contain user identity information
- Tokens are automatically refreshed when they expire
- Failed refresh attempts trigger re-authentication
Error Handling
The library defines several error types for different failure scenarios:
Error Types
UnauthenticatedError | User is not logged in |
UnauthorizedError | User lacks required permissions |
InvalidStateError | OAuth state parameter mismatch |
InvalidIdTokenError | ID token validation failed |
RefreshRequestError | Token refresh failed |
TokenRequestError | Token exchange failed |
InitOidcError | OIDC initialization failed |
DiscoveryFailedError | OIDC discovery endpoint failed |
Error Handling Example
import { UnauthenticatedError, UnauthorizedError } from "@bonniernews/bn-oidc-connector";
app.use((err, req, res, next) => {
if (err instanceof UnauthenticatedError) {
return res.status(401).json({ error: "Please log in" });
}
if (err instanceof UnauthorizedError) {
return res.status(403).json({ error: "Access denied" });
}
next(err);
});
For more information and updates, visit the GitHub repository.