银行OIDC认证演示应用

一个基于React + Vite的银行应用演示,展示使用WSO2 Identity Server进行OIDC认证和WSO2 API Manager进行安全API调用的全流程。

  • by lkokila
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 lkokila/banking-demo-wso2-apim-is-mcp

使用说明

Bank OIDC + APIM Demo — npm only

A React + Vite banking application that demonstrates:

  • OpenID Connect (OIDC) authentication with WSO2 Identity Server (IS) using Authorization Code + PKCE flow
  • Email OTP authentication for transaction authorization
  • Secure API calls to WSO2 API Manager (APIM) with bearer tokens
  • Multi-client OAuth2 scenarios (login client vs. transaction client)

Quick Start

npm install
npm start
# Opens http://localhost:5173 (React app)
# Also starts the chat API service on http://localhost:3002

Alternative (Vite only, without chat service):

npm install
npm run dev
# Opens http://localhost:5173 (React app only)

Configuration

1. Edit src/config.ts

Update the following values based on your WSO2 setup:

export const OIDC_CONFIG = {
  AUTHORIZATION_ENDPOINT: "https://localhost:9444/oauth2/authorize",
  TOKEN_ENDPOINT: "https://localhost:9444/oauth2/token",
  USERINFO_ENDPOINT: "https://localhost:9444/oauth2/userinfo",
  ENDSESSION_ENDPOINT: "https://localhost:9444/oidc/logout",
  CLIENT_ID: "YOUR_CLIENT_ID_FOR_LOGIN",        // Primary client for login & account API calls (This application can have the required login flow including MFA and Branding)
  CLIENT_ID2: "YOUR_CLIENT_ID_FOR_TRANSACTIONS", // Secondary client for email OTP transactions
  REDIRECT_URI: "http://localhost:5173",
  SCOPE: "openid profile email",
  BANK_NAME: "Bitwave Bank"
};

export const API_CONFIG = {
  GET_ACCOUNTS_URL: "https://localhost:8243/accountDetails/1/getAccounts",
  ADD_TRANSACTION_URL: "https://localhost:8243/internalTransfer/1/addTransaction",
  RECENT_TRANSACTIONS_URL: "https://localhost:8243/RecentTransactions/1/recentTransactions"
};

WSO2 Identity Server (IS) Setup

Start IS with Port Offset

WSO2 IS must run on port 9444 (port offset = 1):

cd <IS_HOME>/bin
./wso2server.sh -Dport.offset=1
# Windows: wso2server.bat -Dport.offset=1

This ensures IS listens on https://localhost:9444.

Register Two OAuth2 Clients

Create two separate SPA applications in IS:

Client 1: Login & Account API Calls

  • Application Name: bank-oidc-app
  • Protocol: OpenID Connect
  • Application Type: Single Page Application
  • Redirect URI: http://localhost:5173
  • Allowed Grant Types: Authorization Code
  • Token Endpoint Authentication: None (public client with PKCE)
  • PKCE: Mandatory (S256)
  • Scopes: openid, profile, email
  • Client ID: HpfVbYONf5MwRREA12p14vNQfJAa (or generated)
  • Store this as CLIENT_ID in config.ts

Client 2: Email OTP Transactions

  • Application Name: bank-otp-app
  • Protocol: OpenID Connect
  • Application Type: Single Page Application
  • Redirect URI: http://localhost:5173
  • Allowed Grant Types: Authorization Code
  • Token Endpoint Authentication: None (public client with PKCE)
  • PKCE: Mandatory (S256)
  • Scopes: openid, internal_login, email
  • Client ID: E0bqe3TldZqJ3befDzav0OQkPtIa (or generated)
  • Store this as CLIENT_ID2 in config.ts

Enable Email OTP

Add the following config in the IS/repository/conf/deployment.toml file to enable email OTP with mailtrap https://mailtrap.io/ Add username, password from the mailtrap Sandbox

[output_adapter.email]
from_address= "otp@bitwave.com"
username= "<mailtrap_sandbox_username>"
password= "<mailtrap_sandbox_password>"
hostname= "sandbox.smtp.mailtrap.io"
port= 2525
enable_start_tls= true
enable_authentication= true
signature = "bitwave.com"

WSO2 API Manager (APIM) Setup

Start APIM

cd <APIM_HOME>/bin
./api-manager.sh
# Windows: api-manager.bat
# APIM runs on https://localhost:8243

Configure IS as Key Manager (OAuth2 Provider)

Follow instructions in the below document https://apim.docs.wso2.com/en/latest/api-security/key-management/third-party-key-managers/configure-wso2is7-connector/

In APIM Admin Console:

  1. Navigate to Settings > Key Manager
  2. Add a new Key Manager with these details:
    • Name: WSO2-IS
    • Key Manager Type: WSO2-Identity-Server
    • Server URL: https://localhost:9444
    • Introspection URL: https://localhost:9444/oauth2/introspect
    • Token URL: https://localhost:9444/oauth2/token
    • Revoke URL: https://localhost:9444/oauth2/revoke
    • Userinfo URL: https://localhost:9444/oauth2/userinfo
    • Client ID & Secret: (from an APIM-IS service account, if required)
  3. Click Save
  4. Set this Key Manager as Default (if not already)

Create APIs in APIM

Publish the following APIs in APIM to match the endpoints in config.ts. The appropriate backends for the APIs can be implemented using any language/framework.

API 1: Account Details

  • Name: AccountDetails
  • Context: /accountDetails
  • Version: 1
  • Resource Path: /1/getAccounts
  • Method: POST
  • Backend URL: (mock service or your bank backend)
  • Security: OAuth2

API 2: Internal Transfer

  • Name: InternalTransfer
  • Context: /internalTransfer
  • Version: 1
  • Resource Path: /1/addTransaction
  • Method: POST
  • Backend URL: (mock service or your bank backend)
  • Security: OAuth2

API 3: Recent Transactions

  • Name: RecentTransactions
  • Context: /RecentTransactions
  • Version: 1
  • Resource Path: /1/recentTransactions
  • Method: POST
  • Backend URL: (mock service or your bank backend)
  • Security: OAuth2

Enable CORS

For each API in APIM:

  1. Navigate to Runtime settings
  2. Enable CORS and add:
    • Access-Control-Allow-Origins: http://localhost:5173
    • Access-Control-Allow-Headers: Content-Type, Authorization
    • Access-Control-Allow-Methods: POST, OPTIONS

TLS Certificate Trust

Since both IS and APIM use self-signed certificates:

  1. Browser: Accept or install the self-signed certificates for:
    • https://localhost:9444 (IS)
    • https://localhost:8243 (APIM)
  2. Fetch API: May require additional certificate configuration depending on your environment.

App Features

  • Login Flow: OpenID Connect with PKCE Authorization Code flow
  • Account Overview: Fetches account balances from APIM
  • Recent Transactions: Displays transaction history (collapsible section)
  • Secure Transfer: Email OTP-protected transaction using secondary client
  • Session Management: Token refresh and RP-initiated logout
  • Chat Assistant: Floating chat panel for transaction inquiries (powered by Gemini LLM)

What's Happening: Authentication & API Flows

1. Initial Login (PKCE Authorization Code Flow)

When you click "Sign in to Bitwave Bank", this flow occurs:

Step 1: Client generates PKCE parameters

Code Verifier: 64-character random string (cryptographically secure)
Code Challenge: SHA-256 hash of code verifier (Base64URL encoded)
State: 24-character random string (CSRF protection)

These are stored in sessionStorage for later retrieval.

Step 2: Redirect to IS Authorization Endpoint

GET https://localhost:9444/oauth2/authorize
  ?client_id=HpfVbYONf5MwRREA12p14vNQfJAa
  &response_type=code
  &redirect_uri=http://localhost:5173
  &scope=openid%20profile%20email
  &state=<random_state>
  &code_challenge=<sha256_hash>
  &code_challenge_method=S256

You authenticate with IS (username/password). IS verifies PKCE challenge.

Step 3: Authorization Code Callback

IS redirects back to your app with an authorization code:

GET http://localhost:5173?code=AUTHORIZATION_CODE&state=<returned_state>

Step 4: Token Exchange (Client-side)

The app automatically exchanges the code for tokens:

POST https://localhost:9444/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code=AUTHORIZATION_CODE
redirect_uri=http://localhost:5173
client_id=HpfVbYONf5MwRREA12p14vNQfJAa
code_verifier=<original_code_verifier>

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "6a87e6f8-4c89-3e84-b02a-a374b31d0e98",
  "token_type": "Bearer",
  "expires_in": 3600
}

Tokens are stored in sessionStorage for persistence across page reloads.


2. Fetching User Info & Accounts

Step 1: Fetch User Profile

GET https://localhost:9444/oauth2/userinfo
Authorization: Bearer <access_token>

Response: User profile claims (sub, email, given_name, etc.)

Step 2: Fetch Account Details via APIM

GET https://localhost:8243/accountDetails/1/getAccounts
Authorization: Bearer <access_token>
Accept: application/json

Response:

[
  { "name": "Savings Account", "balance": 50000 },
  { "name": "Current Account", "balance": 125000 }
]

This happens automatically after login via the useEffect hook that watches for token changes.


3. Email OTP Flow (Transaction Authorization)

When you click "Secure Transfer" to authorize a transaction, this multi-step OTP flow occurs:

Step 1: Initiate OTP Flow

POST https://localhost:9444/oauth2/authorize/
Content-Type: application/x-www-form-urlencoded

client_id=E0bqe3TldZqJ3befDzav0OQkPtIa
response_type=code
redirect_uri=http://localhost:5173
state=logpg
scope=openid%20internal_login%20profile
response_mode=direct
username=<extracted_from_id_token>

Key points:

  • Uses CLIENT_ID2 (secondary client for OTP flows)
  • response_mode=direct returns response directly (no redirect)
  • Username is extracted from the id_token JWT payload

Response:

{
  "flowId": "session-id-123",
  "nextStep": {
    "authenticators": [
      {
        "authenticatorId": "email-otp-abc",
        "metadata": {
          "promptType": "USER_PROMPT",
          "params": {}
        }
      }
    ]
  },
  "links": [
    { "rel": "authentication", "href": "https://localhost:9444/oauth2/authn" }
  ]
}

The app sends an OTP to your registered email address.

Step 2: Submit OTP Code

You enter the OTP from your email, and the app submits it:

POST https://localhost:9444/oauth2/authn
Content-Type: application/json

{
  "flowId": "session-id-123",
  "selectedAuthenticator": {
    "authenticatorId": "email-otp-abc",
    "params": {
      "OTPCode": "123456"
    }
  }
}

Response (on success):

{
  "flowStatus": "SUCCESS_COMPLETED",
  "authData": {
    "code": "AUTHORIZATION_CODE_FROM_OTP"
  }
}

Step 3: Exchange OTP Authorization Code for Tokens

The app automatically exchanges the OTP-authenticated code for new tokens:

POST https://localhost:9444/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code=AUTHORIZATION_CODE_FROM_OTP
redirect_uri=http://localhost:5173
client_id=E0bqe3TldZqJ3befDzav0OQkPtIa

New tokens are stored (marked with _client_id: CLIENT_ID2).

Step 4: Submit Transaction via APIM

With the OTP-authenticated tokens, the app submits the transaction:

POST https://localhost:8243/internalTransfer/1/addTransaction
Authorization: Bearer <access_token_from_otp>
Content-Type: application/json

{
  "Date": "2025-03-10",
  "Description": "Internal Transaction",
  "Amount": 50000
}

Response: Transaction confirmation (or error if validation fails)


4. Fetching Recent Transactions

POST https://localhost:8243/RecentTransactions/1/recentTransactions
Authorization: Bearer <access_token>
Content-Type: application/json

Response:

[
  {
    "Date": "2025-03-10",
    "Description": "Deposit",
    "Amount": 10000
  },
  {
    "Date": "2025-03-09",
    "Description": "Withdrawal",
    "Amount": 5000
  }
]

5. Logout (RP-Initiated)

When you click "Sign Out":

GET https://localhost:9444/oidc/logout
  ?id_token_hint=<id_token>
  &post_logout_redirect_uri=http://localhost:5173

IS clears the server-side session and redirects back. Client-side tokens in sessionStorage are cleared.


API Endpoints Summary

Purpose Method Endpoint Headers Notes
User Authentication POST /oauth2/authorize - Initiates OAuth2 flow
Get Tokens POST /oauth2/token Content-Type: x-www-form-urlencoded Authorization code → tokens
User Profile GET /oauth2/userinfo Authorization: Bearer Returns user claims
OTP Initiation POST /oauth2/authorize/ Content-Type: x-www-form-urlencoded Direct response mode
OTP Verification POST /oauth2/authn Content-Type: application/json Submit OTP code
Get Accounts GET /accountDetails/1/getAccounts Authorization: Bearer APIM endpoint
Add Transaction POST /internalTransfer/1/addTransaction Authorization: Bearer, Content-Type: application/json APIM endpoint (OTP-secured)
Recent Transactions POST /RecentTransactions/1/recentTransactions Authorization: Bearer APIM endpoint
Logout GET /oidc/logout - RP-initiated logout

How to Set Up: Step-by-Step

Prerequisites

  • Node.js 16+ installed
  • WSO2 Identity Server (IS) v7.x
  • WSO2 API Manager (APIM) v4.x
  • npm installed

Step 1: Start WSO2 Identity Server

cd <IS_HOME>/bin
./wso2server.sh -Dport.offset=1
# On Windows: wso2server.bat -Dport.offset=1

IS will start on https://localhost:9444.

Step 2: Create OAuth2 Clients in IS

  1. Open IS Admin Console: https://localhost:9444/carbon
  2. Go to Service Providers > Add and create two applications:

Application 1: bank-oidc-app

  • Protocol: OpenID Connect
  • Application Type: Single Page Application
  • Redirect URI: http://localhost:5173
  • Grant Types: Authorization Code
  • PKCE: Mandatory (S256)
  • Scopes: openid, profile, email
  • Copy the generated Client ID → paste into config.ts as CLIENT_ID

Application 2: bank-otp-app

  • Protocol: OpenID Connect
  • Application Type: Single Page Application
  • Redirect URI: http://localhost:5173
  • Grant Types: Authorization Code
  • PKCE: Mandatory (S256)
  • Scopes: openid, internal_login, email
  • Copy the generated Client ID → paste into config.ts as CLIENT_ID2

Step 3: Configure Email OTP in IS

Edit <IS_HOME>/repository/conf/deployment.toml:

[output_adapter.email]
from_address= "otp@bitwave.com"
username= "<mailtrap_sandbox_username>"
password= "<mailtrap_sandbox_password>"
hostname= "sandbox.smtp.mailtrap.io"
port= 2525
enable_start_tls= true
enable_authentication= true
signature = "bitwave.com"

(Get credentials from mailtrap.io)

Step 4: Start WSO2 API Manager

cd <APIM_HOME>/bin
./api-manager.sh
# On Windows: api-manager.bat

APIM will start on https://localhost:8243.

Step 5: Configure IS as Key Manager in APIM

  1. Open APIM Admin Console: https://localhost:8243/admin
  2. Go to Settings > Key Manager → Add Key Manager
  3. Fill in:
    • Name: WSO2-IS
    • Key Manager Type: WSO2-Identity-Server
    • Server URL: https://localhost:9444
    • Introspection URL: https://localhost:9444/oauth2/introspect
    • Token URL: https://localhost:9444/oauth2/token
    • Revoke URL: https://localhost:9444/oauth2/revoke
    • Userinfo URL: https://localhost:9444/oauth2/userinfo
  4. Click Save and set as Default

Step 6: Create APIs in APIM

For each API below, go to API Management > Create API:

API 1: Account Details

  • Name: AccountDetails
  • Context: /accountDetails
  • Version: 1
  • Endpoint: http://your-backend:port/accounts (mock or real backend)
  • Resource:
    • Path: /getAccounts
    • Method: GET
    • Security: OAuth2
  • Publish

API 2: Internal Transfer

  • Name: InternalTransfer
  • Context: /internalTransfer
  • Version: 1
  • Endpoint: http://your-backend:port/transfer (mock or real backend)
  • Resource:
    • Path: /addTransaction
    • Method: POST
    • Security: OAuth2
  • Publish

API 3: Recent Transactions

  • Name: RecentTransactions
  • Context: /RecentTransactions
  • Version: 1
  • Endpoint: http://your-backend:port/transactions (mock or real backend)
  • Resource:
    • Path: /recentTransactions
    • Method: POST
    • Security: OAuth2
  • Publish

For each API, enable CORS in Runtime settings:

  • Access-Control-Allow-Origins: http://localhost:5173
  • Access-Control-Allow-Headers: Content-Type, Authorization
  • Access-Control-Allow-Methods: GET, POST, OPTIONS

Step 7: Update Configuration

Edit src/config.ts:

export const OIDC_CONFIG = {
  CLIENT_ID: "<paste_client_1_id_here>",
  CLIENT_ID2: "<paste_client_2_id_here>",
  BANK_NAME: "Bitwave Bank"
  // Other endpoints should already be correct (localhost ports)
};

Step 8: Install Dependencies & Run

Option 1: Full Setup (with Chat Service)

npm install
npm start

This starts:

  • React App on http://localhost:5173 (Vite dev server)
  • Chat API Service on http://localhost:3002 (Node.js backend)

Both services run concurrently using concurrently.

Option 2: Vite Only (without Chat Service)

npm install
npm run dev

This starts only the React app on http://localhost:5173 (chat features will be unavailable).

Open http://localhost:5173 in your browser and click "Sign in to Bitwave Bank".


Dev-Mode Notes

  • Use npm start to run both Vite (port 5173) and Chat API (port 3002) concurrently, or npm run dev for Vite only.
  • The chat API service must have its dependencies installed. If missing, run: cd api-chat && npm install
  • React StrictMode can double-invoke effects in development. This app prevents duplicate token exchanges using a ref gate and session storage tracking.
  • Tokens are persisted in sessionStorage for page reloads within the same session.
  • All sensitive operations (OTP, transactions) use bearer tokens from the configured Key Manager.
  • The app extracts the username from the id_token JWT claim to auto-populate the OTP flow.
  • Port conflicts: If 5173 or 3002 are already in use, the dev servers will use the next available port.

Troubleshooting

Issue Solution
Port already in use Change port offset or kill the process using the port
"Redirect URI mismatch" Ensure redirect URI in IS matches http://localhost:5173 exactly
Token exchange fails Verify IS is running on 9444 and PKCE is enabled for the client
"Missing bearer token" error Ensure Key Manager is configured in APIM and tokens are valid
CORS errors Enable CORS on APIs in APIM for http://localhost:5173
Certificate errors Accept self-signed certificates in browser or configure trusted CA