使用说明
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_IDin 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_ID2in 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:
- Navigate to Settings > Key Manager
- 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)
- Name:
- Click Save
- 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:
- Navigate to Runtime settings
- Enable CORS and add:
- Access-Control-Allow-Origins:
http://localhost:5173 - Access-Control-Allow-Headers:
Content-Type, Authorization - Access-Control-Allow-Methods:
POST, OPTIONS
- Access-Control-Allow-Origins:
TLS Certificate Trust
Since both IS and APIM use self-signed certificates:
- Browser: Accept or install the self-signed certificates for:
https://localhost:9444(IS)https://localhost:8243(APIM)
- 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=directreturns response directly (no redirect)- Username is extracted from the
id_tokenJWT 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
- Open IS Admin Console:
https://localhost:9444/carbon - 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.tsasCLIENT_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.tsasCLIENT_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
- Open APIM Admin Console:
https://localhost:8243/admin - Go to Settings > Key Manager → Add Key Manager
- 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
- Name:
- 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
- Path:
- 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
- Path:
- 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
- Path:
- 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 startto run both Vite (port 5173) and Chat API (port 3002) concurrently, ornpm run devfor 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
sessionStoragefor 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_tokenJWT 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 |