Skip to content
Open
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
12 changes: 12 additions & 0 deletions authd-oidc-brokers/conf/variants/oidc/broker.conf
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ client_id = <CLIENT_ID>
## if the identity provider is unreachable (e.g. due to network issues).
#force_provider_authentication = false

## Disable local password authentication, requiring users to always perform
## device authentication with the identity provider.
##
## When enabled:
## - Users will not be able to create or use local passwords
## - Device authentication will be required for every login
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
## - Device authentication will be required for every login
## - Device authentication will be required for every authentication attempt,
## including logins and privileged operations (e.g. sudo, polkit actions)

## - Local password authentication mode will not be offered
Copy link
Contributor

Choose a reason for hiding this comment

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

Note to self: We should probably point users to force_provider_authentication here, and maybe briefly discuss the use cases of force_provider_authentication and disable_local_password.

Copy link
Contributor Author

@shiv-tyagi shiv-tyagi Feb 2, 2026

Choose a reason for hiding this comment

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

Yes, force_provider_authentication only comes into play when the user tries logging in through local password.

Maybe we can add an explanation mentioning about when force_provider_authentication makes sense.

##
## Important: Enabling this option prevents offline login entirely.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
## Important: Enabling this option prevents offline login entirely.
## Important: Enabling this option prevents offline authentication entirely.

## Users must have network connectivity to authenticate.
#disable_local_password = false

[users]
## The directory where the home directories of new users are created.
## Existing users will keep their current home directory.
Expand Down
14 changes: 12 additions & 2 deletions authd-oidc-brokers/internal/broker/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ func (b *Broker) availableAuthModes(session session) (availableModes []string, e
func (b *Broker) authModeIsAvailable(session session, authMode string) bool {
switch authMode {
case authmodes.Password:
if b.cfg.disableLocalPassword {
log.Debugf(context.Background(), "Local password authentication is disabled")
return false
}

if !tokenExists(session) {
log.Debugf(context.Background(), "Token does not exist for user %q, so local password authentication is not available", session.username)
return false
Expand Down Expand Up @@ -716,9 +721,14 @@ func (b *Broker) deviceAuth(ctx context.Context, session *session) (string, isAu
// Store the auth info in the session so that we can use it when handling the
// next IsAuthenticated call for the new password mode.
session.authInfo = authInfo
session.nextAuthModes = []string{authmodes.NewPassword}

return AuthNext, nil
// Only require password creation if local password authentication is not disabled
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Only require password creation if local password authentication is not disabled
// Require password creation unless local password authentication is disabled

if !b.cfg.disableLocalPassword {
session.nextAuthModes = []string{authmodes.NewPassword}
return AuthNext, nil
}
Comment on lines +726 to +729
Copy link
Contributor

Choose a reason for hiding this comment

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

Another consequence of not creating a local password is that the keyring can't be unlocked. #1190 also won't fix it in that case.

Copy link
Contributor Author

@shiv-tyagi shiv-tyagi Feb 2, 2026

Choose a reason for hiding this comment

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

Okay. We can discuss about this. I will post here if I get an idea about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I made a proposal here: #1190 (comment)

Comment on lines +726 to +729
Copy link
Contributor

Choose a reason for hiding this comment

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

Note to self: Manually test:

  • ssh
  • sudo
  • pkexec
  • unlocking keyring

Ideally we should add e2e tests for those.


return b.finishAuth(session, authInfo)
}

func (b *Broker) passwordAuth(ctx context.Context, session *session, secret string) (string, isAuthenticatedDataResponse) {
Expand Down
21 changes: 21 additions & 0 deletions authd-oidc-brokers/internal/broker/broker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ func TestIsAuthenticated(t *testing.T) {
sessionOffline bool
username string
forceProviderAuthentication bool
disableLocalPassword bool
userDoesNotBecomeOwner bool
allUsersAllowed bool
extraGroups []string
Expand Down Expand Up @@ -700,6 +701,14 @@ func TestIsAuthenticated(t *testing.T) {
},
address: "127.0.0.1:31315",
},
"Authenticating_with_device_auth_completes_without_newpassword_when_local_password_is_disabled": {
firstSecret: "-",
disableLocalPassword: true,
},
"Authenticating_with_qrcode_completes_without_newpassword_when_local_password_is_disabled": {
firstSecret: "-",
disableLocalPassword: true,
},

"Error_when_authentication_data_is_invalid": {invalidAuthData: true},
"Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true},
Expand Down Expand Up @@ -776,6 +785,17 @@ func TestIsAuthenticated(t *testing.T) {
token: &tokenOptions{deviceIsDisabled: true},
sessionOffline: true,
},
"Error_when_mode_is_password_and_local_password_is_disabled": {
firstMode: authmodes.Password,
disableLocalPassword: true,
token: &tokenOptions{},
},
"Error_when_session_is_for_changing_password_and_local_password_is_disabled": {
sessionMode: sessionmode.ChangePassword,
firstMode: authmodes.Password,
disableLocalPassword: true,
token: &tokenOptions{},
},
"Error_when_mode_is_invalid": {firstMode: "invalid"},
}
for name, tc := range tests {
Expand Down Expand Up @@ -805,6 +825,7 @@ func TestIsAuthenticated(t *testing.T) {
firstUserBecomesOwner: !tc.userDoesNotBecomeOwner,
allUsersAllowed: tc.allUsersAllowed,
forceProviderAuthentication: tc.forceProviderAuthentication,
disableLocalPassword: tc.disableLocalPassword,
extraGroups: tc.extraGroups,
ownerExtraGroups: tc.ownerExtraGroups,
supportsDeviceRegistration: tc.providerSupportsDeviceRegistration,
Expand Down
10 changes: 10 additions & 0 deletions authd-oidc-brokers/internal/broker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
const (
// forceProviderAuthenticationKey is the key in the config file for the option to force provider authentication during login.
forceProviderAuthenticationKey = "force_provider_authentication"
// disableLocalPasswordKey is the key in the config file for the option to disable local password authentication.
disableLocalPasswordKey = "disable_local_password"

// oidcSection is the section name in the config file for the OIDC specific configuration.
oidcSection = "oidc"
Expand Down Expand Up @@ -80,6 +82,7 @@ type userConfig struct {
issuerURL string

forceProviderAuthentication bool
disableLocalPassword bool
registerDevice bool

allowedUsers map[string]struct{}
Expand Down Expand Up @@ -234,6 +237,13 @@ func parseConfig(cfgContent []byte, dropInContent []any, p provider) (userConfig
return userConfig{}, fmt.Errorf("error parsing '%s': %w", forceProviderAuthenticationKey, err)
}
}

if oidc.HasKey(disableLocalPasswordKey) {
cfg.disableLocalPassword, err = oidc.Key(disableLocalPasswordKey).Bool()
if err != nil {
return userConfig{}, fmt.Errorf("error parsing '%s': %w", disableLocalPasswordKey, err)
}
}
}

entraID := iniCfg.Section(entraIDSection)
Expand Down
23 changes: 16 additions & 7 deletions authd-oidc-brokers/internal/broker/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,25 @@ issuer = https://issuer.url.com
client_id = client_id
force_provider_authentication = true
extra_scopes = groups,offline_access, some_other_scope
disable_local_password = true

[users]
home_base_dir = /home
allowed_ssh_suffixes = @issuer.url.com
`,

"invalid_boolean_value": `
"invalid_force_provider_authentication_boolean_value": `
[oidc]
issuer = https://issuer.url.com
client_id = client_id
force_provider_authentication = invalid
`,

"invalid_disable_local_password_boolean_value": `
[oidc]
issuer = https://issuer.url.com
client_id = client_id
disable_local_password = invalid
`,

"singles": `
Expand Down Expand Up @@ -82,12 +90,13 @@ func TestParseConfig(t *testing.T) {

"Do_not_fail_if_values_contain_a_single_template_delimiter": {configType: "singles"},

"Error_if_file_does_not_exist": {configType: "inexistent", wantErr: true},
"Error_if_file_is_unreadable": {configType: "unreadable", wantErr: true},
"Error_if_file_is_not_updated": {configType: "template", wantErr: true},
"Error_if_drop_in_directory_is_unreadable": {dropInType: "unreadable-dir", wantErr: true},
"Error_if_drop_in_file_is_unreadable": {dropInType: "unreadable-file", wantErr: true},
"Error_if_config_contains_invalid_values": {configType: "invalid_boolean_value", wantErr: true},
"Error_if_file_does_not_exist": {configType: "inexistent", wantErr: true},
"Error_if_file_is_unreadable": {configType: "unreadable", wantErr: true},
"Error_if_file_is_not_updated": {configType: "template", wantErr: true},
"Error_if_drop_in_directory_is_unreadable": {dropInType: "unreadable-dir", wantErr: true},
"Error_if_drop_in_file_is_unreadable": {dropInType: "unreadable-file", wantErr: true},
"Error_if_force_provider_authentication_contains_invalid_boolean_value": {configType: "invalid_force_provider_authentication_boolean_value", wantErr: true},
"Error_if_disable_local_password_contains_invalid_boolean_value": {configType: "invalid_disable_local_password_boolean_value", wantErr: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions authd-oidc-brokers/internal/broker/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ func (cfg *Config) SetForceProviderAuthentication(value bool) {
cfg.forceProviderAuthentication = value
}

func (cfg *Config) SetDisableLocalPassword(value bool) {
cfg.disableLocalPassword = value
}

func (cfg *Config) SetRegisterDevice(value bool) {
cfg.registerDevice = value
}
Expand Down
4 changes: 4 additions & 0 deletions authd-oidc-brokers/internal/broker/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type brokerForTestConfig struct {
broker.Config
issuerURL string
forceProviderAuthentication bool
disableLocalPassword bool
registerDevice bool
allowedUsers map[string]struct{}
allUsersAllowed bool
Expand Down Expand Up @@ -60,6 +61,9 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker
if cfg.forceProviderAuthentication {
cfg.SetForceProviderAuthentication(cfg.forceProviderAuthentication)
}
if cfg.disableLocalPassword {
cfg.SetDisableLocalPassword(cfg.disableLocalPassword)
}
if cfg.registerDevice {
cfg.SetRegisterDevice(cfg.registerDevice)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a token
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
access: granted
data: '{"userinfo":{"name":"[email protected]","uuid":"test-user-id","dir":"/home/[email protected]","shell":"/usr/bin/bash","gecos":"[email protected]","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}'
err: <nil>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a token
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
access: granted
data: '{"userinfo":{"name":"[email protected]","uuid":"test-user-id","dir":"/home/[email protected]","shell":"/usr/bin/bash","gecos":"[email protected]","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}'
err: <nil>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a hashed password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a token
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
access: granted
data: '{"userinfo":{"name":"[email protected]","uuid":"test-user-id","dir":"/home/[email protected]","shell":"/usr/bin/bash","gecos":"[email protected]","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}'
err: <nil>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a hashed password
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Definitely a token
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
access: next
data: '{}'
err: <nil>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ clientID=<CLIENT_ID
clientSecret=
issuerURL=https://ISSUER_URL>
forceProviderAuthentication=false
disableLocalPassword=false
registerDevice=false
allowedUsers=map[]
allUsersAllowed=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ clientID=client_id
clientSecret=
issuerURL=https://issuer.url.com
forceProviderAuthentication=false
disableLocalPassword=false
registerDevice=false
allowedUsers=map[]
allUsersAllowed=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ clientID=client_id
clientSecret=
issuerURL=https://issuer.url.com
forceProviderAuthentication=true
disableLocalPassword=true
registerDevice=false
allowedUsers=map[]
allUsersAllowed=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ clientID=lower_precedence_client_id
clientSecret=
issuerURL=https://higher-precedence-issuer.url.com
forceProviderAuthentication=true
disableLocalPassword=true
registerDevice=false
allowedUsers=map[]
allUsersAllowed=false
Expand Down
Loading