DFO OAuth client secret rotation without downtime

How do I correctly to rotate OAuth client secrets for a custom DFO channel without dropping active WebSocket connections? I am using node-oauth2-server to manage tokens and calling POST /api/v2/authorization/token with the new secret, but existing sessions fail validation until a hard restart. Need a zero-downtime swap sequence.

Yep, this is a known issue with stateful WebSocket connections when rotating credentials. The problem is that node-oauth2-server validates the token against the secret present in memory at the time of issuance. When you swap the secret, the old tokens become invalid immediately because the verification logic fails.

To achieve zero downtime, you need to implement a dual-secret validation window. Update your token verification middleware to accept both the old and new secrets for a short grace period. Here is the logic pattern using passport-oauth2:

const secrets = [OLD_SECRET, NEW_SECRET];

passport.use(new OAuth2Strategy({
 authorizationURL: '/oauth/authorize',
 tokenURL: '/oauth/token',
 clientID: 'your-client-id',
 clientSecret: secrets[1], // Use new for issuing
 callbackURL: '/auth/callback'
},
// Verification callback must check both
function(accessToken, refreshToken, profile, cb) {
 // Validate token signature against both secrets
 const isValid = secrets.some(s => verifyToken(accessToken, s));
 return cb(null, isValid ? profile : null);
}
));

Warning: Ensure you revoke the old secret immediately after the grace period to prevent replay attacks. This approach allows existing WebSocket sessions to continue authenticating while new tokens are issued with the new secret.

Have you tried validating token issuance timestamps against a sliding window? Cause: Rotating secrets breaks active WebSocket sessions because node-oauth2-server validates against the current secret only. Solution: Implement a dual-secret check in middleware to accept tokens signed by either secret for a 15-minute overlap.

if (verifyToken(token, newSecret) || (verifyToken(token, oldSecret) && token.iat > cutoff)) {
 res.status(200).json({ valid: true });
} else {
 res.status(401).json({ error: 'Invalid token' });
}

This seems like a standard rotation pattern but misses the infra side. handling secrets in memory is fine, but managing the rollout across dev, staging, and prod requires strict variable control. we use terraform workspaces to promote the new secret hash while keeping the old one active in the provider config for a grace period.

resource "genesyscloud_oauth_client" "dfo_client" {
 name = "dfo-channel"
 secret = var.oauth_new_secret
 old_secret = var.oauth_old_secret # custom data source for overlap
}

the key is not restarting the node service. instead, update the node-oauth2-server middleware to check both secrets. once all websockets reconnect with the new token, remove the old secret from the terraform state and run a plan to confirm drift. this prevents the 403 storm during the swap. i usually stagger this across environments using a simple shell script to wait for active session counts to drop before proceeding to the next workspace.