Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions web/rainmaker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

### Changed - Session-Based Authentication Migration

**Date:** 2025-12-03

#### Overview
Migrated from token-based authentication (storing access tokens in localStorage) to session-based authentication (using HttpOnly cookies managed by the server). This change improves security by:
- Removing token storage from client-side localStorage
- Using HttpOnly cookies that are not accessible via JavaScript
- Passing session ID in request headers instead of request body

#### Breaking Changes

1. **Token Storage Removed**
- `setAccessToken()` and `setRefreshToken()` are now no-op functions (they log warnings)
- Tokens are no longer stored in localStorage
- Session is managed via HttpOnly cookies set by the server

2. **API Request Format Changed**
- `authToken` field removed from `RequestInfo` object in request body
- Session ID now passed in `Session-Id` HTTP header
- All axios instances configured with `withCredentials: true` for cookie support

3. **Redux State Changes**
- `token` field removed from auth reducer state
- Authentication state now relies on `hasValidSession()` instead of checking for access token

#### Files Modified

**Core Authentication Utilities:**
- `dev-packages/egov-ui-kit-dev/src/utils/localStorageUtils/index.js`
- Added `getSessionId()` - reads session from cookies
- Added `hasValidSession()` - checks for valid session
- Modified `getAccessToken()` - now returns session ID for backward compatibility
- Modified `setAccessToken()` and `setRefreshToken()` - now no-op with warnings
- Modified `clearUserDetails()` - now also clears session cookies

**API Request Wrappers:**
- `dev-packages/egov-ui-kit-dev/src/utils/api.js`
- Added `getSessionHeaders()` helper
- Added axios request interceptor to inject Session-Id header
- Modified `wrapRequestBody()` - removed authToken from RequestInfo
- Modified `loginRequest()` - added `withCredentials: true`
- Modified `uploadFile()` - added session headers and credentials
- Modified `commonApiPost()` - removed authToken handling
- Changed error type from `INVALID_TOKEN` to `SESSION_EXPIRED`

- `packages/citizen/src/ui-utils/api.js`
- Added session header management
- Removed authToken from RequestInfo

- `dev-packages/egov-ui-framework-core/src/ui-utils/api.js`
- Added session header management
- Removed authToken from RequestInfo

**Redux Auth Module:**
- `dev-packages/egov-ui-kit-dev/src/redux/auth/actions.js`
- Modified `authenticated()` - no longer stores tokens
- Modified `refreshTokenRequest()` - now triggers logout (deprecated)
- Modified `logout()` - simplified to clear session and redirect

- `dev-packages/egov-ui-kit-dev/src/redux/auth/reducer.js`
- Removed `token` from initial state and action handlers
- Now uses `hasValidSession()` for initial auth check

- `dev-packages/egov-ui-kit-dev/src/redux/auth/middleware.js`
- Removed authToken from RequestInfo objects
- Changed to trigger logout on SESSION_EXPIRED error

**Utility Functions:**
- `dev-packages/egov-ui-kit-dev/src/utils/commons.js`
- Added `hasSessionExpired()` function
- Modified `hasTokenExpired()` to call `hasSessionExpired()` for backward compatibility
- Updated imports to use `getSessionId`

**Higher-Order Components:**
- `dev-packages/egov-ui-kit-dev/src/hocs/withData.js`
- Changed to use `hasValidSession()` instead of `getAccessToken()`

**Trade Licence Module:**
- `dev-packages/egov-tradelicence-dev/src/ui-config/screens/specs/utils/localStorageUtils/index.js`
- Added session-based authentication functions (mirrors main localStorageUtils)

#### Migration Notes

1. **Backend Requirements**
- Server must set session cookie on successful login (primary: `SESSION_ID`, alternatives: `sessionId`, `JSESSIONID`, `session-id`)
- Server must validate `Session-Id` header on API requests
- Server must handle logout by invalidating session and clearing cookie
- Cookies should be set with `HttpOnly`, `Secure`, and `SameSite` attributes

2. **CORS Configuration**
- Server must allow credentials in CORS configuration
- `Access-Control-Allow-Credentials: true`
- `Access-Control-Allow-Origin` must be specific origin (not `*`)

3. **Backward Compatibility**
- `getAccessToken()` still works but returns session ID
- `setAccessToken()` and `setRefreshToken()` log deprecation warnings
- `hasTokenExpired()` still works but calls `hasSessionExpired()`

#### Security Improvements

- **XSS Protection:** Tokens no longer accessible via `localStorage` or JavaScript
- **CSRF Protection:** Session cookies can be configured with `SameSite` attribute
- **Token Theft Prevention:** HttpOnly cookies cannot be stolen via XSS attacks
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
const appName = process.env.REACT_APP_NAME;

// Cookie utility functions for session-based authentication
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
};

const appName = process.env.REACT_APP_NAME;
const deleteCookie = (name) => {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
};

//GET methods
// Session-based authentication - Get session ID from cookie
export const getSessionId = () => {
return getCookie('SESSION_ID') || getCookie('sessionId') || getCookie('JSESSIONID') || getCookie('session-id');
};

// Deprecated: kept for backward compatibility during migration
// Returns session ID instead of access token (no token storage)
export const getAccessToken = () => {
return localStorageGet(`token`);
return getSessionId();
};

// Check if user has valid session
export const hasValidSession = () => {
const userInfo = getUserInfo();
const sessionId = getSessionId();
return !!(userInfo && sessionId);
};

//GET methods
export const getUserInfo = () => {
return localStorageGet("user-info");
};
Expand All @@ -32,12 +57,18 @@ export const getStoredModulesList = () =>{
export const setUserInfo = (userInfo) => {
localStorageSet("user-info", userInfo, null);
};

// Deprecated: No-op functions - tokens are no longer stored
// Session is managed via HttpOnly cookies set by the server
export const setAccessToken = (token) => {
localStorageSet("token", token, null);
// No-op: Session is managed via cookies, not localStorage
console.warn('setAccessToken is deprecated. Session is managed via cookies.');
};
export const setRefreshToken = (refreshToken) => {
localStorageSet("refresh-token", refreshToken, null);
// No-op: Refresh tokens are no longer used with session-based auth
console.warn('setRefreshToken is deprecated. Session is managed via cookies.');
};

export const setTenantId = (tenantId) => {
localStorageSet("tenant-id", tenantId, null);
};
Expand All @@ -56,9 +87,15 @@ export const setStoredModulesList =(storedModuleList) =>{

//Remove Items (LOGOUT)
export const clearUserDetails = () => {
// Clear localStorage items
Object.keys(localStorage).forEach((key) => {
window.localStorage.removeItem(key);
});
// Clear session cookies
deleteCookie('SESSION_ID');
deleteCookie('sessionId');
deleteCookie('JSESSIONID');
deleteCookie('session-id');
};
//Role specific get-set Methods
export const localStorageGet = (key, path) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,40 @@ import { fetchFromLocalStorage, addQueryArg, getDateInEpoch, isPublicSearch } fr
import { toggleSpinner } from "egov-ui-framework/ui-redux/screen-configuration/actions";
import store from "../ui-redux/store";
import {
getAccessToken,
getSessionId,
getTenantId,
getLocale
} from "egov-ui-kit/utils/localStorageUtils";

// Get session-based headers for API requests
const getSessionHeaders = () => {
const sessionId = getSessionId();
const headers = {
"Content-Type": "application/json"
};
if (sessionId) {
headers['Session-Id'] = sessionId;
}
return headers;
};

const instance = axios.create({
baseURL: window.location.origin,
headers: {
"Content-Type": "application/json"
headers: getSessionHeaders(),
withCredentials: true // Enable cookies to be sent with requests
});

// Update headers before each request to include latest session ID
instance.interceptors.request.use((config) => {
const sessionId = getSessionId();
if (sessionId && !isPublicSearch()) {
config.headers['Session-Id'] = sessionId;
}
return config;
});

const wrapRequestBody = (requestBody, action) => {
const authToken = getAccessToken();
// Session ID is now passed in headers, not in request body
let RequestInfo = {
apiId: "Mihy",
ver: ".01",
Expand All @@ -25,10 +45,9 @@ const wrapRequestBody = (requestBody, action) => {
did: "1",
key: "",
msgId: `20170310130900|${getLocale()}`,
requesterId: "",
authToken: authToken
requesterId: ""
// authToken removed - session is managed via cookies/headers
};
if(isPublicSearch()) delete RequestInfo.authToken;
return Object.assign(
{},
{
Expand Down
13 changes: 7 additions & 6 deletions web/rainmaker/dev-packages/egov-ui-kit-dev/src/hocs/withData.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import { fetchpgrConstants } from "egov-ui-kit/redux/common/actions";
import { fetchUiCommonConfig, fetchUiCommonConstants } from "egov-ui-kit/redux/app/actions";
import commonConfig from "config/common";
import { fetchGeneralMDMSData } from "egov-ui-kit/redux/common/actions";
import { getAccessToken } from "egov-ui-kit/utils/localStorageUtils";
import { hasValidSession } from "egov-ui-kit/utils/localStorageUtils";
import { generalMDMSDataRequestObj, getGeneralMDMSDataDropdownName } from "egov-ui-kit/utils/commons";

const withData = (Component) => {
class Wrapper extends React.Component {
componentDidMount() {
const { searchUser, fetchComplaintCategories, authenticated, fetchpgrConstants, fetchUiCommonConfig, fetchUiCommonConstants, fetchGeneralMDMSData } = this.props;
if (getAccessToken()) {

// Check for valid session instead of access token
if (hasValidSession()) {

searchUser();
fetchUiCommonConstants();
fetchComplaintCategories();
/* fetchpgrConstants();
fetchUiCommonConfig();
fetchGeneralMDMSData();

fetchGeneralMDMSData();
*/

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { prepareFormData } from "egov-ui-kit/utils/commons";
import get from "lodash/get";
import {
setTenantId,
getAccessToken,
getSessionId,
setUserInfo,
setAccessToken,
setRefreshToken,
localStorageSet,
localStorageGet,
clearUserDetails,
Expand Down Expand Up @@ -59,22 +57,21 @@ export const authenticating = () => {
return { type: authType.AUTHENTICATING };
};

// Session-based authentication - tokens are no longer stored client-side
// Session is managed via HttpOnly cookies set by the server
export const authenticated = (payload = {}) => {
const userInfo = fixUserDob(payload["UserRequest"]);
const accessToken = payload.access_token;
const refreshToken = payload.refresh_token;
const expiresIn = payload.expires_in;
const lastLoginTime = new Date().getTime();

// Store user info (not tokens - session managed via cookies)
setUserInfo(JSON.stringify(userInfo));
setAccessToken(accessToken);
setRefreshToken(refreshToken);
setTenantId(userInfo.tenantId);
localStorageSet("expires-in", expiresIn);
localStorageSet("last-login-time", lastLoginTime);
localStorageSet("CITIZEN.CITY",userInfo.permanentCity);
localStorageSet("CITIZEN.CITY", userInfo.permanentCity);

return { type: authType.AUTHENTICATED, userInfo, accessToken };
// Note: access_token and refresh_token are no longer stored
// Session is managed via HttpOnly cookies set by the server
return { type: authType.AUTHENTICATED, userInfo, authenticated: true };
};

export const authenticationFailed = () => {
Expand Down Expand Up @@ -104,20 +101,14 @@ export const searchUser = () => {
};
};

// Deprecated: Session refresh is now handled by the server via cookies
// This function is kept for backward compatibility but will trigger logout on session expiry
export const refreshTokenRequest = () => {
return async (dispatch) => {
const refreshToken = localStorageGet("refresh-token");
const grantType = "refresh_token";
const userType = process.env.REACT_APP_NAME === "Citizen" ? "CITIZEN" : "EMPLOYEE";
try {
const response = await loginRequest(null, null, refreshToken, grantType, "", userType);
delete response.ResponseInfo;
dispatch(authenticated(response));
// only option for the time being!
window.location.reload();
} catch (error) {
dispatch(logout());
}
// With session-based auth, session refresh is handled by the server
// If session expires, user must re-authenticate
console.warn('refreshTokenRequest is deprecated. Session is managed via server cookies.');
dispatch(logout());
};
};

Expand All @@ -136,28 +127,28 @@ export const sendOTP = (intent) => {
};
};

// Session-based logout - clears local data and calls server logout endpoint
export const logout = () => {
return async () => {
try {
const authToken = getAccessToken();
if (authToken) {
const response = await httpRequest(AUTH.LOGOUT.URL, AUTH.LOGOUT.ACTION, [{ key: "access_token", value: authToken }]);
} else {
clearUserDetails();
process.env.REACT_APP_NAME === "Citizen"
? window.location.replace(`${window.basename}/user/register`)
: window.location.replace(`${window.basename}/user/login`);
return;
const sessionId = getSessionId();
if (sessionId) {
// Call server logout endpoint to invalidate session
// Server will clear the session cookie
await httpRequest(AUTH.LOGOUT.URL, AUTH.LOGOUT.ACTION, []);
}
} catch (error) {
console.log(error);
clearUserDetails();
console.log('Logout request error:', error);
// Continue with local cleanup even if server request fails
}
// whatever happens the client should clear the user details
// let userInfo=getUserInfo();
// let userRole=get(userInfo,"roles[0].code");

// Clear all local user data and cookies
clearUserDetails();
// window.location.replace(`${window.basename}/user/login`)
window.location.replace(`${window.basename}/user/login`);

// Redirect to appropriate login page
const redirectUrl = process.env.REACT_APP_NAME === "Citizen"
? `${window.basename}/user/register`
: `${window.basename}/user/login`;
window.location.replace(redirectUrl);
};
};
Loading