Fix: OIDC users losing credentials after re-login due to non-persistent encryption keys #363
Reference in New Issue
Block a user
Delete Branch "fix/oidc-credential-persistence"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fix: OIDC users losing credentials after re-login due to non-persistent encryption keys
🐛 Problem
OIDC-authenticated users were experiencing critical data loss where their saved credentials (SSH keys, passwords) would disappear after logging out and logging back in, or after server restarts. This affected all users authenticating via external OIDC providers.
🔄 Update: Race Condition Fix Applied
Commit
9906b94includes additional critical fixes based on code review feedback:📊 Issue Flow - BEFORE Fix
🔴 Root Cause
Problem: Each login generates a completely new random DEK with no persistence mechanism.
✅ Solution Flow - AFTER Fix
🔧 Technical Changes
Modified:
src/backend/utils/user-crypto.ts1.
setupOIDCUserEncryption()- Now Persists the DEK2.
authenticateOIDCUser()- Properly Retrieves Persisted DEK🔐 Encryption Architecture
Before vs After Comparison
Encryption Flow (After Fix)
🧪 Testing
Manual Testing Steps
Expected Results
📈 Impact
Severity: 🔴 HIGH - Critical Data Loss
Users Affected
Backward Compatibility
Migration Path
🔒 Security Considerations
✅ Security Improvements
🔐 Security Properties Maintained
⚠️ Important Security Note
Set a strong
OIDC_SYSTEM_SECRETenvironment variable:This secret is critical for OIDC user data encryption. Do not change this value after deployment as it will make all OIDC user data unrecoverable.
Generate a strong secret:
📝 Checklist
🔗 Related Issues
Resolves the critical issue: "OIDC credentials get wiped out after re-login"
📚 Additional Context
Why OIDC Users Had Different Behavior
OIDC users don't have a password to derive encryption keys from. The original implementation likely intended to use a temporary in-memory key for the session, but this approach caused data loss across sessions. The fix implements a system-derived key that:
This brings OIDC users to feature parity with password-based users while maintaining security.
📊 Performance Metrics
Commits:
d73d2cf- Fix OIDC credential persistence issue (initial fix)9906b94- Fix race condition and remove redundant kekSalt for OIDC users (critical improvements)Files Changed:
src/backend/utils/user-crypto.ts(1 file, cumulative: +48 insertions, -25 deletions)Branch:
fix/oidc-credential-persistenceSummary of Changes
Hello @thorved, I'm Gemini Code Assist1 ! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request resolves a critical issue where OIDC-authenticated users would lose their saved credentials, such as SSH keys and passwords, after logging out, re-logging in, or server restarts. The core problem stemmed from Data Encryption Keys (DEKs) for OIDC users being generated randomly and stored only in memory, leading to data loss when the session ended. The solution introduces a robust persistence mechanism by encrypting these DEKs with a deterministically derived system key (based on the user ID and a system secret) and storing them in the database. This ensures that the correct DEK can be retrieved upon subsequent logins, allowing credentials to remain accessible and bringing OIDC user data persistence to parity with password-based users.
Highlights
setupOIDCUserEncryptionandauthenticateOIDCUsermethods inuser-crypto.tswere updated to manage the generation, encryption, storage, and retrieval of these persistent DEKs.Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point by creating a comment using either
/gemini <command>or@gemini-code-assist <command>. Below is a summary of the supported commands on the current page./gemini review/gemini summary/gemini helpCustomization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a
.gemini/folder in the base of the repository. Detailed instructions can be found here.Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩︎
Code Review
This pull request correctly addresses a critical data loss issue for OIDC users by ensuring the Data Encryption Key (DEK) is persisted. The overall approach is sound. However, my review has uncovered a new critical race condition in the DEK creation logic that could still lead to data loss during concurrent logins. I have provided a detailed comment with a suggested fix for this. Additionally, I've identified some redundant code related to
kekSaltwhich can be removed to improve code clarity and maintainability.@@ -70,7 +70,43 @@ class UserCrypto {}This new implementation introduces a critical race condition and contains redundant logic.
1. Critical Race Condition (Data Loss):
If two concurrent login requests for the same new OIDC user arrive, both can enter the
elseblock to create a new DEK. ThestoreEncryptedDEKfunction performs a non-atomicselect-then-update/insert, which can cause one request's DEK to overwrite the other's in the database. If a user's session uses a DEK that is different from the one stored, any data they save will be unrecoverable on their next login. This is a critical data loss scenario.2. Redundant
kekSaltLogic:The
kekSaltis generated, stored, and checked, but it's not used for OIDC key derivation (deriveOIDCSystemKeyuses theuserIdas the salt). This adds unnecessary complexity and database writes.Suggested Fix:
The following suggestion addresses both issues:
kekSalthandling.The check for
kekSaltis redundant for OIDC users. ThekekSaltis not used in the OIDC key derivation process, asderiveOIDCSystemKeyuses theuserIdas its salt. Relying solely onencryptedDEKto determine if encryption is set up simplifies the code and removes an unnecessary database read.@@ -70,7 +70,43 @@ class UserCrypto {}✅ Changes Made
1. Race Condition Mitigation (Critical Fix)
Problem Identified
You're right - the original implementation had a classic check-then-act race condition. Two concurrent login requests could both generate different DEKs, and whichever stored last would win, leaving the other session with a mismatched key.
Race Condition Scenario:
Solution Implemented
Added a "write-then-read-and-verify" pattern in
setupOIDCUserEncryption():How This Prevents Data Loss:
2. Removed Redundant kekSalt Logic
Problem Identified
Correct - the
kekSaltis generated and stored but never actually used for OIDC users. ThederiveOIDCSystemKey()function usesuserIddirectly as the salt, making the storedkekSaltcompletely redundant.Why It Was Redundant:
Solution Implemented
In
setupOIDCUserEncryption():const kekSalt = await this.generateKEKSalt();await this.storeKEKSalt(userId, kekSalt);const existingKEKSalt = await this.getKEKSalt(userId);In
authenticateOIDCUser():Benefits: