Skip to content

Commit 90c2294

Browse files
committed
fix(security): move GitHub OAuth token from sessionStorage to React state
AuthContext.jsx stored the GitHub OAuth access token in sessionStorage: sessionStorage.setItem(`gh_token_${authUser.uid}`, accessToken) sessionStorage is accessible to any JavaScript running on the page. An XSS injection (e.g. via a third-party script or unsanitised user input) can exfiltrate the token, giving the attacker the same GitHub API access as the logged-in user for the token's lifetime. The comment describing the approach as "securely (non-persistent across tabs for security)" was inaccurate: sessionStorage is still readable by all scripts on the same origin. Changes in src/context/AuthContext.jsx: - Added ghAccessToken state variable (useState(null)). - setGhAccessToken(accessToken) replaces sessionStorage.setItem() on login. - setGhAccessToken(null) replaces sessionStorage.removeItem() on logout. - fetchGitHubStats reads from ghAccessToken state instead of sessionStorage.getItem(). The token remains in React component memory and is never written to any Web Storage API, making it inaccessible to injected scripts. The trade-off is that the token is lost on page refresh, but fetchGitHubStats is called immediately after login so this is acceptable. Closes #80
1 parent 4efa207 commit 90c2294

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

src/context/AuthContext.jsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export const AuthProvider = ({ children }) => {
2222
const [userData, setUserData] = useState(null);
2323
const [loading, setLoading] = useState(true);
2424
const [isOnboarding, setIsOnboarding] = useState(false);
25+
// GitHub OAuth access token kept in React state only -- never written to
26+
// sessionStorage or localStorage. Storing it in Web Storage exposes it to
27+
// any JavaScript running on the page (XSS). Keeping it in memory means it
28+
// is lost on page refresh, but the token is only needed once after login
29+
// to call fetchGitHubStats, so this trade-off is acceptable.
30+
const [ghAccessToken, setGhAccessToken] = useState(null);
2531

2632
// Listen to Auth State Changed
2733
useEffect(() => {
@@ -92,8 +98,8 @@ export const AuthProvider = ({ children }) => {
9298
const githubId = additionalInfo?.profile?.id || null;
9399
const avatar = additionalInfo?.profile?.avatar_url || authUser.photoURL || "";
94100

95-
// Store GitHub accessToken in sessionStorage securely (non-persistent across tabs for security)
96-
sessionStorage.setItem(`gh_token_${authUser.uid}`, accessToken);
101+
// Keep the token in React state only -- do not write to Web Storage.
102+
setGhAccessToken(accessToken);
97103

98104
const userDocRef = doc(db, "users", authUser.uid);
99105
const docSnap = await getDoc(userDocRef);
@@ -146,23 +152,25 @@ export const AuthProvider = ({ children }) => {
146152
const logout = async () => {
147153
setLoading(true);
148154
try {
149-
if (user) {
150-
sessionStorage.removeItem(`gh_token_${user.uid}`);
151-
}
152155
await signOutUser();
153156
setUser(null);
154157
setUserData(null);
155158
setIsOnboarding(false);
159+
setGhAccessToken(null);
156160
} catch (error) {
157161
console.error("Logout failure:", error);
158162
} finally {
159163
setLoading(false);
160164
}
161165
};
162166

163-
// Securely fetches GitHub stats on the client once using the transient OAuth token
167+
// Fetches GitHub stats once after login using the in-memory OAuth token.
168+
// The token is no longer read from sessionStorage -- it comes from the
169+
// ghAccessToken state variable which is populated on login and cleared on
170+
// logout. This prevents any JavaScript on the page from reading the token
171+
// via sessionStorage.getItem().
164172
const fetchGitHubStats = async (uid, username) => {
165-
const token = sessionStorage.getItem(`gh_token_${uid}`);
173+
const token = ghAccessToken;
166174
const headers = token ? { Authorization: `token ${token}` } : {};
167175

168176
try {

0 commit comments

Comments
 (0)