@jack-henry/consumer-tools
v0.0.11
Published
<!-- SPDX-FileCopyrightText: 2025 Jack Henry
Keywords
Readme
@jack-henry/consumer-tools
A collection of Vite plugins and web components designed to help build consumer-facing oauth applications within the Jack Henry ecosystem.
Overview
This package provides essential tools for developing consumer applications including:
- Vite Plugins for development and build-time configuration
- Web Components for consistent UI layouts
- Authentication utilities for OIDC integration
Features
Vite Plugins
- Institution Assets Plugin - Loads institution-specific assets and branding
- Institution Theme Plugin - Applies institution-specific CSS themes and design tokens
- Consumer Layout Plugin - Provides layout structure and routing configuration for consumer apps
- Consumer Auth Plugin - Handles OIDC authentication flow and API proxying
Web Components
- jh-consumer-layout - A layout component that provides consistent structure for consumer applications
- jh-consumer-nav - Navigation component for consumer apps
Installation
yarn add @jack-henry/consumer-toolsPeer Dependencies
This package requires openid-client as a peer dependency for authentication features:
yarn add openid-clientUsage
Using Individual Plugins
import { defineConfig } from 'vite';
import {
institutionAssetsPlugin,
jhThemePlugin,
consumerLayoutPlugin,
consumerAuthPlugin,
} from '@jack-henry/consumer-tools/vite-plugins';
export default defineConfig({
plugins: [
institutionAssetsPlugin({ institutionId: 'your-institution-id' }),
jhThemePlugin({ institutionId: 'your-institution-id' }),
consumerLayoutPlugin({
rootTagName: 'your-app',
institutionId: 'your-institution-id',
routeConfigPath: './src/routing/route-config.ts', // Optional: path to your route configuration
}),
consumerAuthPlugin({
apiBaseUrl: 'https://your-api-base-url.com',
clientConfig: {
client_id: 'your-client-id',
client_secret: 'your-client-secret',
grant_types: ['authorization_code'],
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_post',
redirect_uris: ['https://localhost:8445/auth/cb'],
},
// Optional: customize auth scope
authScope: 'openid profile email https://api.banno.com/consumer/auth/offline_access',
// Optional: customize API headers to copy
apiHeadersToCopy: ['x-request-id'],
}),
],
});Using the Combined Consumer Config
For convenience, you can use the default export which combines all consumer plugins:
import { defineConfig } from 'vite';
import consumerConfig from '@jack-henry/consumer-tools/vite-plugins';
export default defineConfig({
plugins: [
...consumerConfig({
rootTagName: 'your-app',
institutionId: 'your-institution-id',
routeConfigPath: './src/routing/route-config.ts', // Optional: path to your route configuration
auth: {
apiBaseUrl: 'https://your-api-base-url.com',
clientConfig: {
client_id: 'your-client-id',
client_secret: 'your-client-secret',
grant_types: ['authorization_code'],
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_post',
redirect_uris: ['https://localhost:8445/auth/cb'],
},
// Optional: customize auth scope
authScope: 'openid profile email https://api.banno.com/consumer/auth/offline_access',
// Optional: customize API headers to copy
apiHeadersToCopy: ['x-request-id'],
},
}),
],
});Handling Sensitive Credentials
Important: Never commit sensitive credentials like client_secret directly in your code. Use environment variables instead:
import { defineConfig, loadEnv } from 'vite';
import consumerConfig from '@jack-henry/consumer-tools/vite-plugins';
export default defineConfig(async ({ mode }) => {
// Load environment variables (use empty string to load all vars, not just VITE_*)
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
...consumerConfig({
rootTagName: 'your-app',
institutionId: env.INSTITUTION_ID,
auth: {
apiBaseUrl: env.API_URL,
clientConfig: {
client_id: env.CLIENT_ID,
client_secret: env.CLIENT_SECRET,
grant_types: ['authorization_code'],
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_post',
redirect_uris: JSON.parse(env.REDIRECT_URIS),
},
},
}),
],
};
});Create a .env file in your project root:
INSTITUTION_ID=your-institution-id
CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
API_URL=https://your-api-base-url.com
REDIRECT_URIS=["https://localhost:8445/auth/cb"]Note: These variables do not use the VITE_ prefix because they are only used in the Vite configuration and are never exposed to client code. Variables with the VITE_ prefix are embedded into your client bundle and should not contain sensitive information.
Make sure to add .env to your .gitignore:
.env
.env.localUsing Web Components
import '@jack-henry/consumer-tools/components/jh-consumer-layout';
// In your HTML or template
<jh-consumer-layout institutionId="your-institution-id">
<!-- Your app content -->
</jh-consumer-layout>Contexts Provided by jh-consumer-layout
The jh-consumer-layout component provides three contexts using Lit's context API that child components can consume to access shared application state.
User Context
Access the current user's authentication state and information.
Import:
import { consume } from '@lit/context';
import { userContext, type UserContext } from '@jack-henry/consumer-tools/contexts/user';Usage in a Lit component:
@consume({ context: userContext, subscribe: true })
@property({ attribute: false })
user: UserContext;Interface:
interface UserContext {
user: User | null; // User object with profile information
state: 'unauthenticated' | 'authenticated' | 'loading' | 'checking';
}
interface User {
sub: string; // User ID (subject)
given_name: string; // First name
family_name: string; // Last name
name: string; // Full name
email: string; // Email address
nickname: string; // Username/nickname
picture: string; // Profile picture URL
preferred_username: string; // Preferred display name
// ... additional OAuth user claims
}States:
unauthenticated- User is not logged inloading- Initial authentication check in progresschecking- Re-validating authentication statusauthenticated- User is logged in and validated
Institution Context
Access the current financial institution's configuration and branding.
Import:
import { consume } from '@lit/context';
import { institutionContext, type InstitutionContext } from '@jack-henry/consumer-tools/contexts/institution';Usage in a Lit component:
@consume({ context: institutionContext, subscribe: true })
@property({ attribute: false })
institution: InstitutionContext;Interface:
interface InstitutionContext {
institution: Institution | null; // Institution configuration
state: 'initial' | 'loading' | 'ready' | 'error';
}
interface Institution {
// Branding
name: string;
logo: string;
images: {
logoUrl: string;
faviconUrl: string;
// ... additional branding images
};
// Features and abilities
abilities: {
billPay: boolean;
mobileDeposit: boolean;
cardControls: boolean;
// ... many more feature flags
};
// Links and resources
links: InstitutionLink[];
// ... extensive configuration options
}States:
initial- Not yet loadedloading- Fetching institution dataready- Institution data loaded successfullyerror- Failed to load institution data
Router Context
Access the router instance and route configuration.
Import:
import { consume } from '@lit/context';
import { routerContext, type RouterContext } from '@jack-henry/consumer-tools/contexts/router';Usage in a Lit component:
@consume({ context: routerContext, subscribe: true })
@property({ attribute: false })
routerContext: RouterContext;Interface:
interface RouterContext {
router: Router | null; // Router instance for programmatic navigation
config: RouteConfig | null; // Your application's route configuration
}Usage example:
// Navigate programmatically
this.routerContext.router.go('/dashboard');
// Access route configuration
const routes = this.routerContext.config;Complete Component Example
Here's a complete example of a component consuming all three contexts:
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { consume } from '@lit/context';
import { userContext, type UserContext } from '@jack-henry/consumer-tools/contexts/user';
import { institutionContext, type InstitutionContext } from '@jack-henry/consumer-tools/contexts/institution';
import { routerContext, type RouterContext } from '@jack-henry/consumer-tools/contexts/router';
@customElement('my-dashboard')
export class MyDashboard extends LitElement {
@consume({ context: userContext, subscribe: true })
@property({ attribute: false })
user: UserContext;
@consume({ context: institutionContext, subscribe: true })
@property({ attribute: false })
institution: InstitutionContext;
@consume({ context: routerContext, subscribe: true })
@property({ attribute: false })
routerContext: RouterContext;
render() {
if (this.user.state !== 'authenticated') {
return html`<p>Please log in</p>`;
}
return html`
<h1>Welcome, ${this.user.user.given_name}!</h1>
<p>Banking with ${this.institution.institution?.name}</p>
<button @click=${() => this.routerContext.router.go('/settings')}>
Go to Settings
</button>
`;
}
}Note:
router.gois used as an example of programmatic navigation, but that is only here for demonstration purposes. In most cases you should use<a href="/settings">for navigation to ensure proper accessibility, bookmarking, and expected web behavior.
Consumer Auth Plugin Details
The Consumer Auth Plugin provides OIDC authentication functionality for consumer applications, handling the OAuth flow, token management, and API proxying for Banno consumer APIs.
Configuration Options
apiBaseUrl (required)
The base URL for the API endpoints.
clientConfig (required)
The OIDC client configuration object containing:
client_id: Your OIDC client IDclient_secret: Your OIDC client secretgrant_types: OAuth grant types (typically['authorization_code'])response_types: OAuth response types (typically['code'])token_endpoint_auth_method: Authentication method for token endpointredirect_uris: Array of allowed redirect URIs
authScope (optional)
The OAuth scope to request. Defaults to:
'openid profile email https://api.banno.com/consumer/auth/offline_access https://api.banno.com/consumer/auth/accounts.readonly https://api.banno.com/consumer/auth/conversations.readonly'apiHeadersToCopy (optional)
Array of HTTP headers to copy from API responses. Defaults to ['x-request-id'].
Endpoints
The plugin sets up the following endpoints:
/auth- Initiates the OAuth flow/auth/cb- OAuth callback endpoint/validate- Returns current user information/a/conversations/api/*- Proxies to conversations API with authentication/a/consumer/api/*- Proxies to consumer API with authentication
Authentication Flow
- Navigate to
/authto start the OAuth flow - User is redirected to the OAuth provider
- After successful authentication, user is redirected back to
/auth/cb - The plugin stores the access token and redirects to
/ - Subsequent API calls are automatically authenticated
⚠️ Development vs Production
This plugin includes in-memory token storage which is NOT suitable for production use. For production applications, implement proper token storage mechanisms (database, secure session storage, etc.).
Consumer Layout Plugin Details
The Consumer Layout Plugin provides layout structure and routing configuration for consumer applications by automatically wrapping your app's root element with the jh-consumer-layout web component.
Configuration Options
rootTagName (required)
The name of your application's root custom element tag. The plugin will automatically wrap this element with jh-consumer-layout.
institutionId (required)
The institution ID that will be passed to both the layout component and your root application element.
routeConfigPath (optional)
Path to a route configuration file that exports routing information for your application. When provided, the plugin will:
- Import the route configuration module
- Wait for the
jh-consumer-layoutcomponent to be defined - Automatically set the
routeConfigproperty on the layout component
Route Configuration
If you provide a routeConfigPath, your route configuration file should export a default object with your routing information:
// src/routing/route-config.ts
export default {
routes: [
{
path: '/',
component: 'home-view',
title: 'Home',
},
{
path: '/profile',
component: 'profile-view',
title: 'Profile',
},
],
};The route configuration is automatically applied to the jh-consumer-layout component when the page loads, enabling navigation and routing functionality.
HTML Transformation
The plugin performs the following transformations to your HTML:
- If your root tag exists: Wraps it with
jh-consumer-layout - If no root tag found: Creates both the layout wrapper and your root tag inside the
<body> - If route config provided: Injects a module script that loads and applies the routing configuration
Example transformation:
<!-- Before -->
<body>
<your-app></your-app>
</body>
<!-- After -->
<body>
<jh-consumer-layout institution-id="your-institution-id">
<your-app institution-id="your-institution-id"></your-app>
</jh-consumer-layout>
<!-- If routeConfigPath is provided -->
<script type="module">
import routeConfig from './src/routing/route-config.ts';
customElements.whenDefined('jh-consumer-layout').then(() => {
const consumerLayout = document.querySelector('jh-consumer-layout');
if (consumerLayout) {
consumerLayout.routeConfig = routeConfig;
}
});
</script>
</body>Available Exports
Components
@jack-henry/consumer-tools/components/*- Individual consumer application web components
Vite Plugins
@jack-henry/consumer-tools/vite-plugins- All Vite plugins and configurations
Development
Building
yarn buildTesting
yarn testDevelopment Mode
yarn devMigration
If you're migrating from inline OIDC configuration to the consumer auth plugin, see the Migration Guide for detailed instructions.
License
UNLICENSED
