Skip to content

Capgemini Senior Software Engineer – Java Full Stack Interview Preparation

This document consolidates the interview discussion around Authentication & Authorization, React state management, controlled vs uncontrolled components, and custom hooks, tailored for a Senior Software Engineer / Java Full Stack role.

1. Authentication and Authorization in a Full Stack Project

Section titled “1. Authentication and Authorization in a Full Stack Project”

Explain how you’re doing authentication and authorization in your current project.

In our React + Spring Boot application, authentication and authorization are handled separately.

  • Authentication verifies who the user is.
  • Authorization verifies what the authenticated user is allowed to do.

The React frontend interacts with an authentication mechanism, typically an enterprise Identity Provider (IdP) or authentication service. After successful authentication, the application receives a token such as a JWT.

The token can contain or represent:

  • User ID
  • Username
  • Roles
  • Authorities / permissions
  • Expiration time

Example conceptual token payload:

{
"sub": "john",
"roles": ["ADMIN"],
"permissions": [
"CREATE_USER",
"UPDATE_USER",
"DELETE_USER"
],
"exp": 1780000000
}

The frontend uses the authenticated session/token for subsequent API calls. The Spring Boot backend validates the token and performs the actual authorization checks.

flowchart LR
    A[React Login Page] --> B[Authentication Service / IdP]
    B --> C[Authentication Successful]
    C --> D[JWT / Access Token]
    D --> E[React Application]
    E --> F[Axios Interceptor]
    F --> G[API Gateway / Spring Boot]
    G --> H[Spring Security Filter Chain]
    H --> I[Validate Token]
    I --> J[Extract Authorities]
    J --> K[SecurityContext]
    K --> L[Controller]
    L --> M[Service]
    M --> N[Database]

The frontend is not the security boundary.

React can hide buttons, menus, and routes based on permissions, but a user can bypass frontend code by calling the API directly through Postman, curl, or another client.

Therefore:

Authorization must always be enforced on the backend.


2. Authorized User Trying to Access an API Without Permission

Section titled “2. Authorized User Trying to Access an API Without Permission”

Suppose I am an authenticated user, but I try to access an API for which I don’t have permission. How do you handle this in React and Spring Boot?

After login, the application can maintain user information such as:

{
"id": 101,
"name": "John",
"roles": ["USER"],
"permissions": [
"VIEW_REPORT"
]
}

This information can be maintained in:

  • Context API
  • Redux Toolkit
  • Another application-level state mechanism

React can then conditionally render UI elements.

For example:

{user?.permissions.includes("CREATE_USER") && (
<button>Create User</button>
)}

If the user doesn’t have CREATE_USER, the button can be hidden.

Similarly, protected routes can be implemented:

/admin
|
+-- Check authentication
|
+-- Check role
|
+-- Allow / redirect

However, this is only a UX-level protection.

What Happens If the User Calls the API Directly?

Section titled “What Happens If the User Calls the API Directly?”

Suppose the user doesn’t have:

DELETE_USER

but tries:

DELETE /api/users/101

The request still reaches the backend.

Spring Security validates the access token and establishes the authenticated user.

Then authorization is checked.

Example:

@PreAuthorize("hasAuthority('DELETE_USER')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}

If the user has the required authority:

Request
|
v
JWT Validation
|
v
Authority Check
|
v
Controller

If the user does not have the required authority:

Request
|
v
JWT Validation
|
v
Authority Check
|
X
403 Forbidden

The controller should not execute.

Concept Question Example
Authentication Who are you? User successfully logged in
Authorization What can you do? User can view reports but cannot delete users

A useful senior-level distinction:

HTTP Status Meaning
401 Unauthorized Authentication is missing or invalid
403 Forbidden User is authenticated but doesn’t have sufficient permission

In React, how do you manage application state?

React applications usually have different categories of state.

Use useState when state belongs to a single component.

const [name, setName] = useState("");
const [isOpen, setIsOpen] = useState(false);

Typical use cases:

  • Form fields
  • Modal visibility
  • Dropdown state
  • Local UI state

When multiple components need the same state, we can use:

  • Context API
  • Redux Toolkit

Examples:

  • Logged-in user
  • Roles
  • Permissions
  • Theme
  • Language
  • Application configuration

Data retrieved from backend APIs is often better treated as server state.

A solution such as TanStack Query can handle:

  • API fetching
  • Caching
  • Refetching
  • Retry
  • Loading state
  • Error state
  • Cache invalidation
flowchart TD
    A[React Application] --> B[Local State]
    A --> C[Global Client State]
    A --> D[Server State]

    B --> B1[useState]
    B --> B2[useReducer]

    C --> C1[Context API]
    C --> C2[Redux Toolkit]

    D --> D1[TanStack Query]
    D --> D2[API Cache]

I don’t put everything into Redux. I classify state first. Component-specific UI state stays local with useState or useReducer. Cross-cutting state such as authentication, user permissions, theme, or application configuration can use Context API or Redux Toolkit. API data is server state, so for a larger application I prefer a dedicated server-state solution such as TanStack Query. This keeps the architecture maintainable and avoids unnecessary global state.


4. How Context API Stores User Details, Roles, and Permissions

Section titled “4. How Context API Stores User Details, Roles, and Permissions”

Context API is useful when authentication information needs to be consumed by many components.

import { createContext, useContext, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}

The provider can wrap the application:

<AuthProvider>
<App />
</AuthProvider>

After login:

const { setUser } = useAuth();
login(credentials).then((response) => {
setUser(response.user);
});

The user can then be accessed from any component:

const { user } = useAuth();
const isAdmin = user?.roles.includes("ADMIN");
const canCreate =
user?.permissions.includes("CREATE_USER");

Conditional UI:

{canCreate && (
<button>Create User</button>
)}

5. How Redux Toolkit Stores User Details, Roles, and Permissions

Section titled “5. How Redux Toolkit Stores User Details, Roles, and Permissions”

For a larger application, Redux Toolkit provides a centralized and structured state store.

import { createSlice } from "@reduxjs/toolkit";
const authSlice = createSlice({
name: "auth",
initialState: {
token: null,
user: null
},
reducers: {
loginSuccess(state, action) {
state.token = action.payload.token;
state.user = action.payload.user;
},
logout(state) {
state.token = null;
state.user = null;
}
}
});
export const {
loginSuccess,
logout
} = authSlice.actions;
export default authSlice.reducer;

After successful login:

dispatch(loginSuccess(response));

The store conceptually looks like:

Redux Store
|
+-- auth
|
+-- token
|
+-- user
|
+-- id
+-- name
+-- roles
+-- permissions

Read the user:

const user = useSelector(
state => state.auth.user
);

Check a role:

const isAdmin =
user?.roles.includes("ADMIN");

Check a permission:

const canDelete =
user?.permissions.includes("DELETE_USER");

An Axios request interceptor can attach the access token automatically to API requests.

Conceptually:

axios.interceptors.request.use(config => {
const token = getAccessToken();
if (token) {
config.headers.Authorization =
`Bearer ${token}`;
}
return config;
});

Request:

GET /api/users
Authorization: Bearer <access-token>

The backend then validates the token.

Avoid treating browser-accessible storage as a completely secure location for sensitive tokens. The exact strategy depends on the application’s security architecture. For browser applications, secure HttpOnly, Secure, appropriately configured cookies are often preferred for session/token handling where the architecture supports them.


Feature Context API Redux Toolkit
Setup Simple More structured
Best suited for Cross-cutting/simple shared state Complex global state
Authentication Good Good
Theme / language Excellent Usually unnecessary
Complex workflows Limited Strong
DevTools Limited Redux DevTools
Middleware Limited/manual Strong ecosystem
Large application Can become difficult if overused Better suited
State organization Provider-based Central store + slices

Don’t say:

Redux is always better.

Instead say:

I choose based on application complexity. Context API is sufficient for relatively simple cross-cutting concerns such as authentication, theme, or locale. Redux Toolkit becomes more useful when the application has complex shared state, multiple update paths, asynchronous workflows, debugging requirements, or many independent features.


What is the difference between controlled and uncontrolled components in React?

React state controls the input value.

const [name, setName] = useState("");
<input
value={name}
onChange={e => setName(e.target.value)}
/>

Flow:

flowchart LR
    A[User Types] --> B[onChange]
    B --> C[setState]
    C --> D[React State]
    D --> E[Input Value]
  • Easy validation
  • Easy conditional rendering
  • Centralized form state
  • Predictable behavior

Typical use cases:

  • Login form
  • Registration form
  • Search box
  • Complex business forms

The DOM maintains the input value.

const inputRef = useRef();
<input ref={inputRef} />

Read the value:

const value =
inputRef.current.value;

Flow:

flowchart LR
    A[User Types] --> B[DOM Input]
    B --> C[useRef]
    C --> D[Read Value]
  • Simple forms
  • File inputs
  • Integrating with some third-party libraries
  • Situations where you don’t need React state for every keystroke
Controlled Uncontrolled
React owns value DOM owns value
Usually uses useState Usually uses useRef
Easier validation Simpler for basic cases
More React state updates Fewer React state updates
Excellent for complex forms Useful for simple/DOM-oriented scenarios

When should we create a custom hook in React?

A custom hook is useful when we need to reuse stateful React logic across multiple components.

The key point is:

Custom hooks share logic, not the same state instance.

If three components call the same custom hook, each call normally has its own hook state unless the hook internally connects to shared state.


10. Requirements / Scenarios for Custom Hooks

Section titled “10. Requirements / Scenarios for Custom Hooks”

Create a custom hook when you identify repeated behavior such as:

useAuth()

Could expose:

user
login()
logout()
isAuthenticated
hasRole()
hasPermission()
useFetch()

Could manage:

data
loading
error
refetch()
useDebounce()

Useful for:

  • Search
  • Auto-complete
  • API calls triggered by typing
useWindowSize()

Useful when components need responsive JavaScript behavior.

usePagination()

Can encapsulate:

  • Current page
  • Page size
  • Next page
  • Previous page
  • Total pages
useLocalStorage()

Can encapsulate reading/writing browser storage.


import {
useEffect,
useState
} from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(
"Request failed"
);
}
return response.json();
})
.then(result => {
setData(result);
setError(null);
})
.catch(err => {
setError(err);
})
.finally(() => {
setLoading(false);
});
}, [url]);
return {
data,
loading,
error
};
}

Use it in a component:

function EmployeeList() {
const {
data,
loading,
error
} = useFetch("/api/employees");
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Unable to load employees.</p>;
}
return (
<ul>
{data?.map(employee => (
<li key={employee.id}>
{employee.name}
</li>
))}
</ul>
);
}

A particularly useful example for this interview is useAuthorization.

import { useAuth } from "./AuthContext";
export function useAuthorization() {
const { user } = useAuth();
const hasRole = (role) =>
user?.roles?.includes(role) ?? false;
const hasPermission = (permission) =>
user?.permissions?.includes(permission) ?? false;
return {
hasRole,
hasPermission
};
}

Component:

function UserManagement() {
const {
hasPermission
} = useAuthorization();
return (
<>
{hasPermission("CREATE_USER") && (
<button>
Create User
</button>
)}
{hasPermission("DELETE_USER") && (
<button>
Delete User
</button>
)}
</>
);
}

This avoids duplicating permission-checking logic throughout the application.

This only controls the frontend experience.

The backend still needs:

@PreAuthorize(
"hasAuthority('DELETE_USER')"
)

13. Complete React + Spring Boot Security Architecture

Section titled “13. Complete React + Spring Boot Security Architecture”

A senior-level explanation can combine all of the above:

flowchart TB
    U[User] --> R[React Application]

    R --> L[Login]
    L --> IDP[Identity Provider / Auth Service]

    IDP --> T[Access Token / JWT]
    T --> R

    R --> S[Auth Context / Redux Toolkit]
    S --> UI[Menus / Buttons / Protected Routes]

    R --> AX[Axios Interceptor]
    AX --> G[API Gateway / Spring Boot]

    G --> SF[Spring Security Filter Chain]
    SF --> JV[JWT Validation]
    JV --> AC[Authorities / Roles]
    AC --> SC[SecurityContext]

    SC --> AZ{Authorization Check}
    AZ -->|Allowed| C[Controller]
    AZ -->|Denied| F[403 Forbidden]

    C --> SV[Service Layer]
    SV --> DB[(Database)]

If the Capgemini interviewer asks all these questions together, a strong answer would be:

In our React and Spring Boot application, authentication is handled through an authentication service or enterprise Identity Provider. After successful authentication, the client receives an access token, typically a JWT, containing or representing the user’s identity and authorities.

On the React side, we maintain user information such as user ID, roles, and permissions using Context API or Redux Toolkit. This allows components to conditionally render menus, buttons, and protected routes. Axios interceptors can attach the access token to API requests.

However, the React application is not our security boundary. If an authenticated user manually calls an API for which they don’t have permission, the request still reaches Spring Boot. Spring Security validates the token, creates the authenticated security context, and checks the required authority using mechanisms such as @PreAuthorize and hasAuthority(). If the user doesn’t have the required permission, the backend returns 403 Forbidden and the controller is not executed.

For React state management, I classify state into local state, shared client state, and server state. I use useState or useReducer for local state, Context API or Redux Toolkit for shared state, and a server-state solution such as TanStack Query where appropriate for API data.

I create custom hooks when I have reusable stateful logic. For example, authentication logic can be encapsulated in useAuth, permission checks in useAuthorization, and common API behavior in a data-fetching hook. This improves separation of concerns, reduces duplication, and makes components easier to maintain.

For forms, I generally use controlled components when business validation and dynamic behavior are required, while uncontrolled components can be useful for simpler DOM-driven scenarios or specific inputs such as file uploads.


For a Capgemini Senior Software Engineer interview, emphasize these points:

  1. Authentication != Authorization
  2. React authorization is primarily UX-level protection
  3. Spring Boot is the real security enforcement point
  4. 401 generally means authentication failure
  5. 403 means authenticated but insufficient permission
  6. Use roles for coarse-grained access
  7. Use permissions/authorities for fine-grained access
  8. Don’t put every piece of state into Redux
  9. Custom hooks are for reusing stateful logic
  10. Context API and Redux Toolkit solve different levels of application-state complexity
  11. API/server state is different from client state
  12. Never trust permissions supplied only by the frontend
  13. Token expiration and refresh need to be handled
  14. Authorization should also be considered at the service/API boundary
  15. Keep authentication, authorization, state management, and UI concerns separated
Authentication
Who is the user?
Authorization
What can the user do?
React
UX-level permission handling
Axios
Attach access token
Spring Security
Validate token
Authorities / Roles
Authorization
@PreAuthorize
Allow / Deny
403
Insufficient permission