Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions api/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,26 @@ func NewOIDC(conf *config.Configuration, db *database.GormDatabase, userChangeNo
log.Fatal().Err(err).Msg("failed to initialize OIDC provider")
}

externalProvider := provider
if !conf.OIDC.ExternalSecret {
externalProvider, err = rp.NewRelyingPartyOIDC(
context.Background(),
conf.OIDC.Issuer,
conf.OIDC.ClientID,
"", // no secret, PKCE only

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can't you use a public client for the web ui too? Keep GOTIFY_OIDC_CLIENTSECRET unset or set to an empty string. Then we don't need this workaround for Entra.

@gekmihesg gekmihesg Aug 12, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

True, that works as well. The change took some iterations and had more workarounds added but they all proved to be unnecessary and this was the only part left. So, nevermind then, feel free to close this PR.

conf.OIDC.RedirectURL,
conf.OIDC.Scopes,
opts...,
)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize OIDC provider for external flow")
}
}

return &OIDCAPI{
DB: db,
Provider: provider,
ExternalProvider: externalProvider,
UserChangeNotifier: userChangeNotifier,
UsernameClaim: conf.OIDC.UsernameClaim,
PasswordStrength: conf.PassStrength,
Expand Down Expand Up @@ -88,6 +105,7 @@ type pendingElevation struct {
type OIDCAPI struct {
DB *database.GormDatabase
Provider rp.RelyingParty
ExternalProvider rp.RelyingParty
UserChangeNotifier *UserChangeNotifier
UsernameClaim string
PasswordStrength int
Expand Down Expand Up @@ -316,7 +334,7 @@ func (a *OIDCAPI) ExternalAuthorizeHandler(ctx *gin.Context) {
rp.WithCodeChallenge(req.CodeChallenge),
}
ctx.JSON(http.StatusOK, &model.OIDCExternalAuthorizeResponse{
AuthorizeURL: rp.AuthURL(state, a.Provider, authOpts...),
AuthorizeURL: rp.AuthURL(state, a.ExternalProvider, authOpts...),
State: state,
})
}
Expand Down Expand Up @@ -363,12 +381,12 @@ func (a *OIDCAPI) ExternalTokenHandler(ctx *gin.Context) {
rp.CodeExchangeOpt(rp.WithURLParam("redirect_uri", session.RedirectURI)),
rp.WithCodeVerifier(req.CodeVerifier),
}
tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](ctx.Request.Context(), req.Code, a.Provider, exchangeOpts...)
tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](ctx.Request.Context(), req.Code, a.ExternalProvider, exchangeOpts...)
if err != nil {
ctx.AbortWithError(http.StatusUnauthorized, fmt.Errorf("token exchange failed: %w", err))
return
}
info, err := rp.Userinfo[*oidc.UserInfo](ctx.Request.Context(), tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), a.Provider)
info, err := rp.Userinfo[*oidc.UserInfo](ctx.Request.Context(), tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), a.ExternalProvider)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to get user info: %w", err))
return
Expand Down
9 changes: 6 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type OIDC struct {
Issuer string
ClientID string
ClientSecret string
ExternalSecret bool
UsernameClaim string
RedirectURL string
AutoRegister bool
Expand Down Expand Up @@ -112,9 +113,10 @@ func Get() (*Configuration, []FutureLog) {
UploadedImagesDir: "data/images",
PluginsDir: "data/plugins",
OIDC: OIDC{
UsernameClaim: "preferred_username",
AutoRegister: true,
Scopes: []string{"openid", "profile", "email"},
UsernameClaim: "preferred_username",
AutoRegister: true,
Scopes: []string{"openid", "profile", "email"},
ExternalSecret: true,
},
}

Expand Down Expand Up @@ -172,6 +174,7 @@ func Get() (*Configuration, []FutureLog) {
add(parseString(&c.OIDC.Issuer, EnvOIDCIssuer))
add(parseString(&c.OIDC.ClientID, EnvOIDCClientID))
add(parseString(&c.OIDC.ClientSecret, EnvOIDCClientSecret))
add(parseBool(&c.OIDC.ExternalSecret, EnvOIDCExternalSecret))
add(parseString(&c.OIDC.UsernameClaim, EnvOIDCUsernameClaim))
add(parseString(&c.OIDC.RedirectURL, EnvOIDCRedirectURL))
add(parseBool(&c.OIDC.AutoRegister, EnvOIDCAutoRegister))
Expand Down
1 change: 1 addition & 0 deletions config/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
EnvOIDCIssuer = "GOTIFY_OIDC_ISSUER"
EnvOIDCClientID = "GOTIFY_OIDC_CLIENTID"
EnvOIDCClientSecret = "GOTIFY_OIDC_CLIENTSECRET"
EnvOIDCExternalSecret = "GOTIFY_OIDC_EXTERNALSECRET"
EnvOIDCUsernameClaim = "GOTIFY_OIDC_USERNAMECLAIM"
EnvOIDCRedirectURL = "GOTIFY_OIDC_REDIRECTURL"
EnvOIDCAutoRegister = "GOTIFY_OIDC_AUTOREGISTER"
Expand Down