Skip to content

Conversation

@mazlumtoprak
Copy link

This issue is a small proof of concept that fixes #1531. The implementation may not be complete yet but fulfils our personal needs on refresh_tokens

Features:

  • Support offline_access scope to retrieve refresh token
  • OIDC session persistence in database to maintain authentication state across restarts and for the store of the refresh_token
  • Background token refresh mechanism that automatically renews OAuth2 tokens before expiration. Configurable with check_interval and expiry_threshold.
  • Background invalidation mechanism that automatically removes session from expired nodes after session_invalidation_grace_period

Current Limitations:

  • With the implementation of the invalidation mechanism, this PoC is a bit designed for our own needs. This can be enhanced and be more generalized. This means: We want the user to have no SSO relogins while he is online. If he's offline for a longer period (and falls into session_invalidation_grace_period), we want him to do a reauth to enforce our compliance checks via Azure. Therefore we delete the refresh tokens on the invalidation goroutine. With this, we did not implement the retrieval of an access_token using the refresh_token when the user connects again after the session_invalidation_grace_period has passed.
  • have read the CONTRIBUTING.md file
  • raised a GitHub issue or discussed it on the projects chat beforehand (not myself - refering to Enhance OIDC Authentication with refresh_token Support #1531)
  • added unit tests
  • added integration tests --> Have prepared some locally but didn't manage to get them tested correctly. Would need a bit of guidance here.
  • updated documentation if needed
  • updated CHANGELOG.md

@ghost
Copy link

ghost commented Jul 24, 2025

Pull Request Revisions

RevisionDescription
r2
OIDC session management improvements addedImplemented comprehensive OIDC session management including database schema for tracking sessions, token refresh mechanisms, and background jobs for managing token lifecycle and node authentication
r1
Added OIDC token refresh supportIntroduced comprehensive OIDC token refresh mechanism with session tracking, node connection validation, and background refresh job to manage authentication sessions and node registration

✅ AI review completed for r2
Help React with emojis to give feedback on AI-generated reviews:
  • 👍 means the feedback was helpful and actionable
  • 👎 means the feedback was incorrect or unhelpful
💬 Replying to feedback with a comment helps us improve the system. Your input also contributes to shaping future interactions with the AI reviewer.

We'd love to hear from you—reach out anytime at [email protected].

Comment on lines 304 to 307
// Validate that the MachineKey in the Noise session matches the one associated with the NodeKey.
if ns.machineKey != node.MachineKey {
return nil, NewHTTPError(http.StatusNotFound, "node key in request does not match the one associated with this machine key", nil)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hscontrol/noise.go's getAndValidateNode method, consider adding logging when a mismatch between MachineKey and NodeKey is detected. This would help with debugging potential security incidents where someone is attempting to use a valid NodeKey with an incorrect MachineKey.

Comment on lines +497 to +513
if newToken.RefreshToken != "" {
session.RefreshToken = newToken.RefreshToken
} else {
log.Debug().
Str("session_id", session.SessionID).
Msg("OIDC: No new refresh token received, keeping existing one")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In RefreshOIDCSession, there's no explicit handling for token rotation security concerns. Some OIDC providers invalidate old refresh tokens when issuing new ones, but the code doesn't check for this case when newToken.RefreshToken is empty. Consider adding a check to verify with the provider whether the old token is still valid if no new token is received.

Comment on lines +302 to +307
threshold := now.Add(5 * time.Minute)
needsRefresh := tt.session.IsActive &&
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TestNeedsRefresh, the token refresh threshold is hardcoded to 5 minutes, but in the TokenRefreshConfig struct, there's an ExpiryThreshold configuration field. Consider using the configuration value for consistency or explicitly documenting why the test uses a different threshold than what's configurable.

RegistrationID RegistrationID `gorm:"not null"` // For reusing HandleNodeFromAuthPath

// Token data
RefreshToken string `gorm:"type:text"` // TODO: Encrypt?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In hscontrol/types/oidc_session.go, refresh tokens are stored as plaintext with only a TODO comment about encryption. Given that refresh tokens are long-lived security credentials, consider prioritizing the implementation of encryption for these tokens before deploying to production.

Thifhi and others added 5 commits July 24, 2025 16:07
* Improve map auth logic

* Bugfix

* Add comment, improve error message

* noise: make func, get by node

this commit splits the additional validation into a
separate function so it can be reused if we add more
endpoints in the future.

It swaps the check, so we still look up by NodeKey, but before
accepting the connection, we validate the known machinekey from
the db against the noise connection.

The reason for this is that when a node logs in or out, the node key
is replaced and it will no longer be possible to look it up, breaking
reauthentication.

Signed-off-by: Kristoffer Dalby <[email protected]>
Co-authored-by: Kristoffer Dalby <[email protected]>
@mazlumtoprak mazlumtoprak force-pushed the feature/planetexpress/oidc-refresh-tokens branch from 4d8ed21 to a0007a7 Compare July 24, 2025 18:19
Comment on lines +38 to +45
func (s *OIDCSession) IsExpired() bool {
return s.TokenExpiry != nil && s.TokenExpiry.Before(time.Now())
}

// IsExpiringSoon checks if the session's token will expire within the given duration
func (s *OIDCSession) IsExpiringSoon(duration time.Duration) bool {
return s.TokenExpiry != nil && s.TokenExpiry.Before(time.Now().Add(duration))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In IsExpired and IsExpiringSoon methods of OIDCSession, direct calls to time.Now() make unit testing difficult. Consider refactoring to use a testable time source (e.g., via dependency injection or a package variable that can be overridden in tests).

Comment on lines +452 to +462
err = a.state.SaveOIDCSession(existingSession)
if err != nil {
return fmt.Errorf("failed to update OIDC session: %w", err)
}

} else {
// Create new session
err = a.state.CreateOIDCSession(session)
if err != nil {
return fmt.Errorf("failed to create OIDC session: %w", err)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the createOrUpdateOIDCSession function, errors from a.state.SaveOIDCSession and a.state.CreateOIDCSession are wrapped without preserving the original context. Consider adding more detailed information in the error messages to make debugging easier (e.g., include node ID, session ID).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance OIDC Authentication with refresh_token Support

2 participants