A how-to manual: sign in to Ubuntu with Microsoft Entra ID accounts. Register the app, install authd and the broker, decide who may log in, SSH and desktop login, groups and sudo, user management, security and troubleshooting, tested in a lab with the real errors shown.
▶Prefer to watch? This short video runs this page's main commands on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
authd: log in to Ubuntu with a Microsoft Entra ID (Azure AD) account.
sudo add-apt-repository ppa:ubuntu-enterprise-desktop/authd && sudo apt install authd
sudo snap install authd-msentraid
ssh alice@example.com@server
authd is Canonical's login service for cloud identities. Instead of creating a local account for every person, or joining each machine to a domain, you let people sign in with the account they already use for Microsoft 365, through a short device code that they confirm on their phone. The user is then a normal Linux user: it has a UID, a home directory, groups and sudo rights that you can control from Entra groups.
This guide goes in the order you do the work: register the app in Entra, install authd and the broker, connect them to your tenant, decide who may log in, choose the sign-in methods, log in over SSH or the desktop, manage users and groups, and then logs, security and troubleshooting.
Everything that could be run was run, on Ubuntu 24.04.5 with authd 0.7.1 and the authd-msentraid snap 0.5.4, in a lab with systemd and snapd, and the output you see is the real output. One thing could not be run: a successful Entra sign-in, because the lab has no tenant and no person with a phone. Those steps are marked "not run" and come from Canonical's documentation. What the lab did show is every error up to that point, including the real answers from Microsoft for a wrong tenant ID and a wrong client ID. Ubuntu 26.04 and 26.10 were not tested.
What authd is, how it fits together, and what was tested
authd is Canonical's authentication service for Ubuntu that lets people log in with an account from a cloud identity provider, such as Microsoft Entra ID (the new name for Azure Active Directory), instead of a local account or a classic Active Directory join. It runs on Ubuntu Desktop and Server 24.04 LTS or later, on amd64 and arm64. It is free, open source and developed in the open on GitHub (canonical/authd).
The pieces:
authd the daemon (/usr/libexec/authd). Keeps the list of known users and groups, and talks to the brokers.
NSS module lets getent, id and ls see the cloud users as normal Linux users (libnss_authd).
PAM modules sit in the login path: GDM, ssh, login, sudo (pam_authd.so, pam_authd_exec.so).
broker the part that speaks to one identity provider. For Entra ID it is the snap authd-msentraid. Others: authd-google, authd-oidc (Keycloak and similar).
identity provider Microsoft Entra ID itself, in the cloud.
The login runs like this: you give a user name such as alice@example.com, the PAM module asks authd, authd asks the broker which sign-in methods are possible, the broker talks to Entra ID, and when Entra says yes, authd creates the Linux user (UID, home directory) on first login and keeps a cache so that you can log in again when the network is down.
The sign-in is an OAuth 2.0 device authorization grant (RFC 8628): the machine shows a short code and a web address, and you finish the sign-in in a browser on any other device. That is what makes it work over SSH on a server with no browser. It is not Kerberos and it does not join the machine to a domain, so there are no domain controllers, no machine account in a directory and no Group Policy.
THE LAB. Everything below that says "ran" ran for real in a container with systemd and snapd (so that the services and the snap broker behave as on a real machine): Ubuntu 24.04.5, authd 0.7.1~ubuntu24.04.2 from Canonical's stable PPA, the authd-msentraid snap 0.5.4 and snapd 2.77.1.
WHAT COULD NOT BE TESTED, and is marked "not run" wherever it appears: a successful Entra ID sign-in. That needs a real tenant, a registered app and a person with a phone, and the lab has none. So the guide runs everything up to the sign-in screen and the errors that Microsoft really returns, and describes the rest from Canonical's documentation. Also not run: GDM (no desktop in the lab), NFS and Samba, device registration, Ubuntu 26.04 and 26.10 (the lab is 24.04; the differences that the documentation names are noted).
Related guides on this site: Active Directory with SSSD (ad-sssd-tutorial.html) is the classic domain join and is the better choice when you need Kerberos, Group Policy or legacy applications. authd is the lighter choice for Entra-only organisations and for laptops.
Step 1: register the application in Entra ID (not run)
Do this once per tenant in the Microsoft Entra admin center, as an administrator. It is a web page, so it is not run here; the steps are from Canonical's documentation.
1. Go to Entra ID, App registrations, New registration.
2. Give it a name such as "Ubuntu authd". For the account type choose what fits your organisation (the documentation says only to choose the appropriate type for your use case). Click Register.
3. On the overview page write down two values: the Application (client) ID, which the broker calls <CLIENT_ID>, and the Directory (tenant) ID, which the broker calls <ISSUER_ID>.
4. Under Manage, API permissions, set these Microsoft Graph permissions, all of type Delegated:
GroupMember.Read.All read group memberships admin consent: YES
openid sign users in admin consent: no
User.Read sign in and read user profile admin consent: no
Click "Grant admin consent for <your tenant>" so that GroupMember.Read.All is granted.
5. For the device code flow: Manage, Authentication (Preview), Settings, set "Allow public client flows" to Enabled. (Not needed if you only use the Entra authentication flow described later.)
6. For device registration only: Manage, Authentication (Preview), Add Redirect URI, type "Mobile and desktop applications", and enter
https://login.microsoftonline.com/common/oauth2/nativeclient
Keep the two IDs. They are not secrets (they appear in sign-in URLs), but a wrong one is the most common cause of a broken setup, and the errors in the troubleshooting section show what each mistake looks like.
Which users can sign in is also decided in Entra: only accounts in the tenant, and any Conditional Access policy that applies to the application applies to authd logins too. authd adds its own second list on each machine (the allowed users, below).
Step 2: install authd and the Entra ID broker
On Ubuntu 26.04 LTS authd is in the normal archive. On 24.04 LTS add Canonical's stable PPA first. (The lab is 24.04.)
$ lsb_release -ds
Ubuntu 24.04.5 LTS
$ add-apt-repository -y ppa:ubuntu-enterprise-desktop/authd 2>&1 | tail -4
See https://documentation.ubuntu.com/authd/stable/ for more information
More info: https://launchpad.net/~ubuntu-enterprise-desktop/+archive/ubuntu/authd
Adding repository.
$ apt-get install -y authd 2>&1 | tail -4
Created symlink /etc/systemd/system/sockets.target.wants/authd.socket → /usr/lib/systemd/system/authd.socket.
authd.service is a disabled or a static unit, not starting it.
Checking NSS setup...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
On Ubuntu Desktop add gnome-shell and the Yaru theme so that the login screen gets the authd sign-in dialog: sudo apt install authd gnome-shell yaru-theme-gnome-shell (not run, the lab has no desktop). On a server, authd alone is enough.
The broker is a separate snap:
$ snap install authd-msentraid 2>&1 | tail -2
authd-msentraid (0.x/stable) 0.5.4 from Canonical** installed
$ snap list authd-msentraid
Name Version Rev Tracking Publisher Notes
authd-msentraid 0.5.4 503 0.x/stable canonical** -
$ dpkg-query -W -f='${Package} ${Version}\n' authd
authd 0.7.1~ubuntu24.04.2
What the installation added (the package contents, trimmed to the parts that matter):
$ dpkg -L authd | grep -E 'bin/|libexec|systemd/system|security/|libnss|etc/' | grep -v '/etc/pam.d$'
/etc/authd
/etc/authd/authd.yaml
/etc/authd/brokers.d
/etc/pam.d/gdm-authd
/etc/update-manager
/etc/update-manager/release-upgrades.d
/etc/update-manager/release-upgrades.d/authd-release-upgrades.cfg
/usr/bin/authctl
/usr/lib/systemd/system
/usr/lib/systemd/system/authd.service
/usr/lib/systemd/system/authd.socket
/usr/lib/x86_64-linux-gnu/libnss_authd.so.2
/usr/lib/x86_64-linux-gnu/security/pam_authd.so
/usr/lib/x86_64-linux-gnu/security/pam_authd_exec.so
/usr/libexec
/usr/libexec/authd
/usr/libexec/authd-pam
authd is socket-activated: the socket exists right after installation and systemd starts the daemon on the first request.
$ systemctl is-enabled authd.socket
enabled
$ systemctl start authd
$ systemctl is-active authd authd.socket
active
active
$ ls -l /run/authd.sock
srw-rw-rw- 1 root root 0 Oct 19 00:13 /run/authd.sock
The installation also changed three things in the login path. NSS now asks authd after the local files, and PAM asks it as part of the common stacks:
$ grep -E '^(passwd|group|shadow):' /etc/nsswitch.conf
passwd: files authd
group: files authd
shadow: files authd
$ grep -h pam_authd_exec /etc/pam.d/common-auth /etc/pam.d/common-account
auth [success=2 ignore=ignore default=die authinfo_unavail=ignore] pam_authd_exec.so /usr/libexec/authd-pam
account [default=ignore success=ok] pam_authd_exec.so /usr/libexec/authd-pam
Because authd comes after "files", local accounts always win: an Entra user can never take over a name that already exists in /etc/passwd.
The daemon is sandboxed by systemd. A few lines from its unit file, to see what it is allowed to do:
$ systemctl cat authd.service | grep -E '^(PrivateNetwork|NoNewPrivileges|ProtectSystem|CapabilityBoundingSet|StateDirectory=)'
NoNewPrivileges=true
PrivateNetwork=yes
StateDirectory=authd
ProtectSystem=true
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_SYS_PTRACE
PrivateNetwork=yes means the daemon itself has no network at all. Only the broker (which has the snap network interface) talks to Microsoft.
Step 3: connect the broker to your tenant
Two things connect authd to Entra ID: telling authd that the broker exists, and telling the broker which tenant and app to use.
1. Register the broker with authd by copying its small description file into /etc/authd/brokers.d/. The daemon reads that directory only when it starts, so restart it afterwards.
$ ls /etc/authd/brokers.d/
$ cp /snap/authd-msentraid/current/conf/authd/msentraid.conf /etc/authd/brokers.d/
$ cat /etc/authd/brokers.d/msentraid.conf
# This section is used by authd to identify and communicate with the broker.
# It should not be edited.
[authd]
name = Microsoft Entra ID
brand_icon = /snap/authd-msentraid/current/broker_icon.png
dbus_name = com.ubuntu.authd.MSEntraID
dbus_object = /com/ubuntu/authd/MSEntraID
$ systemctl restart authd
(That file says "It should not be edited": it only tells authd the broker's name and its D-Bus address.)
2. Put your two IDs into the broker's own configuration file, /var/snap/authd-msentraid/current/broker.conf. It ships as a template with placeholders. These are the values from the app registration in Step 1; the ones used here are made-up so that the errors later are honest (a real tenant is not needed to start the broker).
$ grep -E '^(issuer|client_id)' /var/snap/authd-msentraid/current/broker.conf
issuer = https://login.microsoftonline.com/<ISSUER_ID>/v2.0
client_id = <CLIENT_ID>
$ sed -i 's|<ISSUER_ID>|11111111-2222-3333-4444-555555555555|; s|<CLIENT_ID>|aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee|' /var/snap/authd-msentraid/current/broker.conf
$ grep -E '^(issuer|client_id)' /var/snap/authd-msentraid/current/broker.conf
issuer = https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0
client_id = aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
3. Restart the broker whenever its file changes. On Ubuntu 26.04 and later the documentation gives systemctl restart -v with the unit name (the -v prints the log). On 24.04 restart it and check its status:
$ systemctl restart snap.authd-msentraid.authd-msentraid.service
$ systemctl is-active snap.authd-msentraid.authd-msentraid.service
active
$ snap services authd-msentraid
Service Startup Current Notes
authd-msentraid.authd-msentraid enabled active dbus-activated
(snap restart authd-msentraid does the same. The broker is "dbus-activated": it also starts by itself when authd asks for it.)
The broker file is read only at start, and it is checked then. A broker that does not like its configuration does not start at all, so the first thing to look at after any change is whether the service is active. The failures are shown in the next sections and in the troubleshooting section.
The same two steps, with the IDs filled in by script, are what the cloud-init and Landscape recipes at the end of this guide do for many machines.
Step 4: decide who may log in on this machine
Having an account in the Entra tenant does not by itself let someone log in to every Ubuntu machine. Each machine has its own list. It lives in the [users] section of the broker file.
$ grep -vE '^\s*(#|$)' /var/snap/authd-msentraid/current/broker.conf
[oidc]
issuer = https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0
client_id = aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
[msentraid]
[users]
[flows]
entra_auth = false
The file is long because it is mostly explanation. That command shows only the active lines.
allowed_users who may log in after Entra says yes. OWNER (the default), ALL, or user names. Comma separated.
owner the owner. If unset, THE FIRST USER WHO LOGS IN becomes the owner automatically.
extra_groups local groups that every authd user is added to at login.
owner_extra_groups local groups only the owner is added to (for example sudo).
home_base_dir where new home directories are created (default /home). Only affects first-time logins.
The default surprises people: the first person to log in is the owner and the only person allowed, so on a shared machine the second user is turned away. Choose one of these:
allowed_users = ALL # anyone Entra accepts (fine for a tenant you control)
allowed_users = alice@example.com,bob@example.com # only these two, nobody is "owner"
allowed_users = OWNER,carol@example.com # the owner plus one more
Here is a realistic setup: the owner and one named user, an extra group for everyone, and sudo and lpadmin for the owner only. Each line is un-commented with sed, then the broker is restarted and checked:
$ sed -i 's/^#allowed_users = OWNER/allowed_users = OWNER,alice@example.com/; s/^#extra_groups =.*/extra_groups = users/; s/^#owner_extra_groups =.*/owner_extra_groups = sudo,lpadmin/' /var/snap/authd-msentraid/current/broker.conf
$ systemctl restart snap.authd-msentraid.authd-msentraid.service; systemctl is-active snap.authd-msentraid.authd-msentraid.service
active
$ sed -n '/^\[users\]/,/^\[flows\]/p' /var/snap/authd-msentraid/current/broker.conf | grep -vE '^\s*(#|$)'
[users]
allowed_users = OWNER,alice@example.com
extra_groups = users
owner_extra_groups = sudo,lpadmin
[flows]
If an administrator logs in first and becomes the owner by accident, make the next person the owner by deleting the drop-in file that authd wrote for it (it comes back at the next successful login):
$ sudo rm /var/snap/authd-msentraid/current/broker.conf.d/20-owner-autoregistration.conf
$ ls /var/snap/authd-msentraid/current/broker.conf.d/
(That directory is empty here, because nobody has logged in. The file appears at the first successful login.)
One more setting belongs to this decision. By default, when the network or Entra is unreachable, a user who has logged in before can still log in with the local password. That also means a user who was disabled in Entra can still log in to a machine that cannot reach Entra. To refuse that, set force_access_check_with_provider = true in the [oidc] section. The price is that nobody can log in during an Entra or network outage. The security section comes back to this.
Sign-in methods: device code, Entra password with MFA, passwordless
The [flows] section decides which sign-in methods the broker offers.
device_code (default on). The machine shows a code and a web address (and a QR code on the desktop). The user finishes the sign-in in a browser on another device. Works for every Entra account type, including accounts with no MFA method set up. Needs "Allow public client flows" in the app registration.
entra_auth (default off in new configurations). Sign-in inside the terminal or login screen: Entra password then an MFA challenge (number matching, one-time code), or passwordless with a FIDO2 security key, Microsoft Authenticator or a Temporary Access Pass. Needs an MFA method enrolled; federated AD FS accounts and accounts without MFA cannot use it. If a user cannot, authd falls back to device code when that is enabled.
A user who signs in without typing the password (passwordless) is asked for a local password on the first login. Only a salted hash of it is stored, and it is what lets them log in offline later.
entra_auth has a precondition. To work out the user's groups, authd reads them from Microsoft Graph, and the sign-in token of this flow does not include that permission. So you need ONE of: device registration switched on (register_device = true), or a client secret configured with the GroupMember.Read.All application permission granted in Entra. Without one of them the broker refuses to start. This is what happens when you switch the flow on without either (the lab restores the setting afterwards):
$ cp /var/snap/authd-msentraid/current/broker.conf /root/broker.conf.keep
$ sed -i 's/^entra_auth = false/entra_auth = true/' /var/snap/authd-msentraid/current/broker.conf
$ systemctl restart snap.authd-msentraid.authd-msentraid.service; sleep 2; systemctl is-active snap.authd-msentraid.authd-msentraid.service
failed
(exit status 3)
$ journalctl -u snap.authd-msentraid.authd-msentraid.service --no-pager -n 4 | cut -c17- | grep invalid
authd-lab authd-msentraid[1234]: invalid configuration: the "entra_auth" flow is enabled in [flows], but it cannot retrieve group memberships from Microsoft Graph without "register_device" enabled or a "client_secret" configured; fix this by either disabling "entra_auth", enabling "register_device", or granting the app the GroupMember.Read.All application permission and configuring a "client_secret"
$ cp /root/broker.conf.keep /var/snap/authd-msentraid/current/broker.conf
Turning every flow off is refused too:
$ sed -i 's/^#device_code = true/device_code = false/' /var/snap/authd-msentraid/current/broker.conf
$ systemctl restart snap.authd-msentraid.authd-msentraid.service; sleep 2; systemctl is-active snap.authd-msentraid.authd-msentraid.service
failed
(exit status 3)
$ journalctl -u snap.authd-msentraid.authd-msentraid.service --no-pager -n 4 | cut -c17- | grep invalid
authd-lab authd-msentraid[1234]: invalid [flows] configuration: no supported authentication flows are enabled; at least one of the "device_code" or "entra_auth" flows must be enabled
$ cp /root/broker.conf.keep /var/snap/authd-msentraid/current/broker.conf
And so is leaving a placeholder in the file:
$ sed -i 's|^client_id = .*|client_id = <CLIENT_ID>|' /var/snap/authd-msentraid/current/broker.conf
$ systemctl restart snap.authd-msentraid.authd-msentraid.service; sleep 2; systemctl is-active snap.authd-msentraid.authd-msentraid.service
failed
(exit status 3)
$ journalctl -u snap.authd-msentraid.authd-msentraid.service --no-pager -n 6 | cut -c17- | grep -A1 'invalid values'
authd-lab authd-msentraid[1234]: config file "/var/snap/authd-msentraid/503/broker.conf" has invalid values, did you edit the config file?
unedited template placeholder found in section 'oidc', key 'client_id'
$ cp /root/broker.conf.keep /var/snap/authd-msentraid/current/broker.conf; systemctl restart snap.authd-msentraid.authd-msentraid.service; systemctl is-active snap.authd-msentraid.authd-msentraid.service
Job for snap.authd-msentraid.authd-msentraid.service failed because the control process exited with error code.
See "systemctl status snap.authd-msentraid.authd-msentraid.service" and "journalctl -xeu snap.authd-msentraid.authd-msentraid.service" for details.
failed
(exit status 3)
That last restart failed too, although the file is good again. After several failed starts in a row systemd refuses to try again for a while ("Start request repeated too quickly"). Clear the failed state and start it again:
$ systemctl reset-failed snap.authd-msentraid.authd-msentraid.service
$ systemctl restart snap.authd-msentraid.authd-msentraid.service; sleep 2; systemctl is-active snap.authd-msentraid.authd-msentraid.service
active
Device registration (register_device = true in [msentraid]) registers the machine as a device in Entra, so that administrators can see it in the Entra admin center. It needs the redirect URI from Step 1, and changing it makes every user sign in again. Not run (it needs a real tenant).
For most small and medium setups, start with the default device code flow: it has the fewest moving parts.
Step 5: log in over SSH
SSH needs two settings and one more broker line. sshd must use PAM and must allow keyboard-interactive prompts (that is how the provider menu and the sign-in code reach the terminal):
$ cat /etc/ssh/sshd_config.d/authd.conf
UsePAM yes
KbdInteractiveAuthentication yes
$ systemctl restart ssh
$ sshd -T | grep -iE '^(usepam|kbdinteractiveauthentication)'
usepam yes
kbdinteractiveauthentication yes
This also turns on PAM-based password login for local accounts even when PasswordAuthentication is off, so on a hardened server use the Match form shown in the security section.
A person who has never logged in on this machine has no account yet, so by default authd does not offer them the provider menu over SSH. First logins over SSH are only allowed for the name suffixes you list in the broker file. Here is what a first login looks like without the setting (the ssh configuration in the lab turns off the host-key prompt):
$ grep ssh_allowed /var/snap/authd-msentraid/current/broker.conf
## ssh_allowed_suffixes_first_auth = @example.com,@anotherexample.org
## ssh_allowed_suffixes_first_auth = *
#ssh_allowed_suffixes_first_auth =
$ ssh alice@example.com@localhost
(alice@example.com@localhost) Password:
(alice@example.com@localhost) Password:
It behaves like an unknown local user: a plain password prompt that can never succeed, and no mention of Entra. Allow the company's suffix (a single * allows every suffix) and restart the broker:
$ sed -i 's/^#ssh_allowed_suffixes_first_auth =.*/ssh_allowed_suffixes_first_auth = @example.com/' /var/snap/authd-msentraid/current/broker.conf
$ grep ssh_allowed /var/snap/authd-msentraid/current/broker.conf
## ssh_allowed_suffixes_first_auth = @example.com,@anotherexample.org
## ssh_allowed_suffixes_first_auth = *
ssh_allowed_suffixes_first_auth = @example.com
$ systemctl restart snap.authd-msentraid.authd-msentraid.service
Now connect with the Entra user name as the SSH user name. The full name, domain included, is the user, so the command has two @ signs:
$ ssh alice@example.com@server.example.com
$ ssh alice@example.com@localhost
== Provider selection ==
1. local
2. Microsoft Entra ID
(alice@example.com@localhost) Choose your provider:
>
(The host here is the lab itself.) The menu is authd's: "local" is the normal local-account login, and "Microsoft Entra ID" is the broker. After choosing the broker, the broker contacts Entra. With the made-up tenant ID from Step 3 it cannot, and the message that comes back is the most misleading one in the whole setup:
$ ssh alice@example.com@localhost
== Provider selection ==
1. local
2. Microsoft Entra ID
(alice@example.com@localhost) Choose your provider:
> 2
Error connecting to provider. Check your network connection.
== Provider selection ==
1. local
2. Microsoft Entra ID
(alice@example.com@localhost) Choose your provider:
>
"Check your network connection" is what the user sees. The real reason is in the journal, and it is Microsoft saying the tenant does not exist:
$ journalctl -u 'snap.authd-*.service' --no-pager -n 3 | cut -c17- | grep 'Error connecting' | cut -c1-230
authd-lab authd-msentraid[1234]: Error connecting to provider: 400 Bad Request: {"error":"invalid_tenant","error_description":"AADSTS90002: Tenant '11111111-2222-3333-4444-555555555555' not found. Check to make sure you have the c
When the message says "check your network connection", read the journal before you check the network. The broker needs to reach login.microsoftonline.com over HTTPS, nothing else. Check that from the machine:
$ curl -s -o /dev/null -w '%{http_code}\n' https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
200
Next, a tenant that does exist but an application ID that does not (a wrong or mistyped client ID). The tenant ID in the output is replaced by <TENANT_ID> in this guide; the lab used an existing public tenant for it.
$ sed -i 's|^issuer = .*|issuer = https://login.microsoftonline.com/<TENANT_ID>/v2.0|' /var/snap/authd-msentraid/current/broker.conf
$ ssh alice@example.com@localhost
== Provider selection ==
1. local
2. Microsoft Entra ID
(alice@example.com@localhost) Choose your provider:
> 2
can't select authentication mode: could not generate device code flow layout: oauth2: "unauthorized_client" "AADSTS700016: Application with identifier 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' was not found in the directory '<TENANT_ID>'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: <trace-id> Correlation ID: <correlation-id> Timestamp: <timestamp>Z" "https://login.microsoftonline.com/error?code=700016"
== Provider selection ==
1. local
2. Microsoft Entra ID
(alice@example.com@localhost) Choose your provider:
>
This one is clear: AADSTS700016, "Application with identifier ... was not found in the directory". The tenant is right, the client ID is not. (Microsoft's trace and correlation IDs are also replaced by placeholders here; you would quote them to Microsoft support.)
What happens with correct IDs, NOT RUN because it needs a real tenant: after choosing the broker the user is shown a web address and a short code (device code flow), opens the address on a phone or laptop, types the code and signs in with MFA as usual. Back in the terminal, a first-time user is asked to create a local password (used for offline logins), and is then logged in; authd creates the account and the home directory. Then on the next login the user can type that local password instead of repeating the device code. The password must pass the local quality rules (libpwquality).
The login timeout is 60 seconds by default, which can be too short when someone has to find their phone. It is LOGIN_TIMEOUT in /etc/login.defs:
$ grep -n '^LOGIN_TIMEOUT' /etc/login.defs
206:LOGIN_TIMEOUT 60
Step 5 (alternative): log in at the desktop (GDM, not run)
The lab has no desktop, so this is from Canonical's documentation. It needs the gnome-shell and Yaru packages from Step 2.
1. On the login screen click "not listed" under the user name field.
2. Type the Entra user name, in the form user@domain.name.
3. Choose the broker "Microsoft Entra ID".
4. If MFA is enabled, the screen shows a QR code and a login code (the device code flow). On a second device scan the code or open the address and finish the sign-in.
5. After success the user is asked for a local password, which is used for later offline logins.
GDM and the GNOME shell need a version with authd's patches. On 24.04 those come from the same PPA; add it before installing gnome-shell.
If the login screen misbehaves, GDM's own log is the first place to look:
$ sudo journalctl /usr/bin/gnome-shell
What an Entra user looks like on the machine
After the first login the user is a normal Linux user. The tools you already use work on it, with the full name (domain included) as the user name. Nobody has logged in yet in the lab, so this shows what you get before that and what authd refuses to invent:
$ getent passwd alice@example.com; echo "exit status $?"
exit status 2
$ id alice@example.com
id: 'alice@example.com': no such user
(exit status 1)
$ AUTHD_NSS_INFO=stderr getent passwd alice@example.com; echo "exit status $?"
00:14:26 INFO [nss_authd::logs] Log output set to stderr
00:14:26 INFO [nss_authd::logs] Log level set to Info
00:14:26 INFO [nss_authd::client] Connecting to authd on /run/authd.sock...
00:14:26 INFO [nss_authd::passwd] error when getting user by name 'alice@example.com': Some requested entity was not found
exit status 2
After a login, the same commands would print the account (not run, there was no sign-in):
$ getent passwd alice@example.com
$ id alice@example.com
Where the files and numbers come from: the user database is /var/lib/authd/authd.sqlite3, owned by root with mode 0700 on the directory. UIDs and GIDs are allocated by authd from 10000 to 60000 (UID_MIN, UID_MAX, GID_MIN, GID_MAX in /etc/authd/authd.yaml). The primary group is a group with the user's name. The numbers are different on every machine, which matters for NFS and Samba (see the problems section).
$ ls -ld /var/lib/authd
drwx------ 2 root root 4096 Oct 19 00:13 /var/lib/authd
$ ls -l /var/lib/authd/authd.sqlite3
-rw------- 1 root root 61440 Oct 19 00:13 /var/lib/authd/authd.sqlite3
$ grep -vE '^\s*(#|$)' /etc/authd/authd.yaml; echo "(no active lines: all defaults)"
(no active lines: all defaults)
GROUPS from Entra (documented; only the Entra broker supports it, and only after a login, so not run). authd reads the groups from Microsoft Graph and maps them to Linux groups by name:
Entra group name Linux group kind
(user's own name) alice@example.com primary group, created automatically
linux-sudo sudo "local" group: the prefix linux- is removed and the user is added to the machine's own group
linux-docker docker same rule (by the prefix rule; the documentation shows sudo as its example)
Azure_OIDC_Test azure_oidc_test any other group: lowercased, as a group managed by authd
So the way to give someone sudo on every Ubuntu machine is to create an Entra group named linux-sudo and add them to it. Treat that group like the root password: whoever can edit it controls sudo everywhere. The groups are re-read at each online login, not continuously, so a removal in Entra shows up on the user's next login to each machine.
The sudo group also appears with the owner: owner_extra_groups = sudo from Step 4 gives the first user sudo without any Entra group.
Managing users with authctl
authctl is the command line for users and groups that authd manages. Its subcommands:
$ authctl --help | sed -n '5,9p'
authctl [command]
Available Commands:
user Commands related to users
group Commands related to groups
$ authctl user --help | sed -n '5,12p'
authctl user [command]
Available Commands:
lock Lock (disable) a user managed by authd
unlock Unlock (enable) a user managed by authd
set-uid Set the UID of a user managed by authd
set-shell Set the login shell for a user
set-home Set the home directory of a user managed by authd
$ authctl group --help | sed -n '5,8p'
authctl group [command]
Available Commands:
set-gid Set the GID of a group managed by authd
user lock / unlock disable and re-enable a user, keeping the account, UID and files
user delete remove the user's record from authd (option --remove-home also removes the home directory)
user set-uid, set-shell, set-home change the numbers and paths after the fact
group set-gid, delete the same for groups
They only work on users that authd already knows, which means after a first login:
$ authctl user lock alice@example.com; echo "exit status $?"
Error: user "alice@example.com" not found
exit status 5
$ authctl user set-shell alice@example.com /bin/zsh; echo "exit status $?"
Error: user "alice@example.com" not found
exit status 5
Deleting a group asks for confirmation and warns about files:
$ echo n | authctl group delete nogroup; echo "exit status $?"
Warning: Deleting a group that still owns files on the filesystem can lead
to security issues.
Any existing files owned by this group may become accessible to a different
group that is later assigned the same GID.
You should manually check that no files remain owned by this group.
Are you sure you want to delete group "nogroup"? [y/N] Aborted.
exit status 0
LOCK a user, to suspend access without deleting anything (documented, not run since there is no logged-in user):
$ sudo authctl user lock alice@example.com
$ sudo loginctl terminate-user alice@example.com
$ sudo authctl user unlock alice@example.com
The lock does not end sessions that are already running, so end them with loginctl. It is matched by the Entra user ID as well, so renaming the user in Entra does not get around it. And it does NOT stop SSH public-key logins: see the next sections.
OFFBOARD a person (documented): first disable or delete them in Entra and revoke their sessions. If force_access_check_with_provider is on, that alone stops new logins; if not, a user can still log in with the local password while the machine cannot reach Entra, so also remove the local account on each machine:
$ sudo loginctl terminate-user alice@example.com
$ sudo authctl user delete --remove-home alice@example.com
$ getent passwd alice@example.com
Careful: authctl user delete frees the UID. A file left behind with that number can later belong to a different person. If you only want to stop logins, lock the account instead, because a lock keeps the UID reserved. Never delete /var/lib/authd/authd.sqlite3 for the same reason: authd would hand out the old numbers again.
Do the Entra step first, or the account is created again at the person's next login.
Logs and debugging
Almost every failure in authd is explained in the journal. The units:
$ journalctl -u authd.service --no-pager -n 3 | cut -c17-
authd-lab authd[1234]: GetAuthenticationModes: Could not get authentication modes for session "<session-id>": Error connecting to provider. Check your network connection.
authd-lab authd[1234]: SelectAuthenticationMode: Could not select authentication mode "device_auth" for session "<session-id>": could not generate device code flow layout: oauth2: "unauthorized_client" "AADSTS700016: Application with identifier 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' was not found in the directory '<TENANT_ID>'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: <trace-id> Correlation ID: <correlation-id> Timestamp: <timestamp>Z" "https://login.microsoftonline.com/error?code=700016"
authd-lab authd[1234]: SetShell: user "alice@example.com" not found
$ journalctl -u authd.service -u 'snap.authd-*.service' --no-pager -n 4 | cut -c17- | cut -c1-160
authd-lab systemd[1]: Starting snap.authd-msentraid.authd-msentraid.service - Service for snap application authd-msentraid.authd-msentraid...
authd-lab systemd[1]: Started snap.authd-msentraid.authd-msentraid.service - Service for snap application authd-msentraid.authd-msentraid.
authd-lab authd[1234]: SelectAuthenticationMode: Could not select authentication mode "device_auth" for session "<session-id>
authd-lab authd[1234]: SetShell: user "alice@example.com" not found
authd itself journalctl -u authd.service
authd and all brokers journalctl -u authd.service -u "snap.authd-*.service"
the Entra broker journalctl -u snap.authd-msentraid.authd-msentraid.service
the login screen journalctl /usr/bin/gnome-shell
More detail, one component at a time. The PAM modules: add debug=true to their lines (the documented one-liner edits every file that names them):
$ grep -c debug=true /etc/pam.d/common-auth
0
(exit status 1)
$ sed -i '/pam_authd_exec\.so\|pam_authd\.so/ s/$/ debug=true/' /etc/pam.d/*
$ grep -c debug=true /etc/pam.d/common-auth
1
$ sed -i 's/ debug=true$//' /etc/pam.d/*
$ grep -c debug=true /etc/pam.d/common-auth
0
(exit status 1)
(The last sed puts the files back.) The NSS module prints its own messages when AUTHD_NSS_INFO=stderr is set, as shown in the previous section. The authd daemon takes a verbosity level (0 errors, 1 info, 2 debug), set in /etc/authd/authd.yaml or with a systemd drop-in that adds -vv:
$ cat /etc/systemd/system/authd.service.d/debug.conf
[Service]
ExecStart=
ExecStart=/usr/libexec/authd -vv
$ systemctl daemon-reload; systemctl restart authd
$ systemctl cat authd.service | grep -A1 '^ExecStart=$'
ExecStart=
ExecStart=/usr/libexec/authd -vv
$ rm -r /etc/systemd/system/authd.service.d; systemctl daemon-reload; systemctl restart authd
The broker takes the same -vv through a drop-in that replaces its ExecStart with /usr/bin/snap run authd-msentraid -vv (not run here; the commands are in the cheat sheet). Turn debug off again afterwards: the debug logs can contain user names and sign-in details.
When asking for help, a bug report needs the journal of authd and the broker, the versions, and the (edited) broker.conf without secrets:
$ snap list authd-msentraid | tail -1
authd-msentraid 0.5.4 503 0.x/stable canonical** -
$ dpkg-query -W -f='${Package} ${Version}\n' authd
authd 0.7.1~ubuntu24.04.2
$ grep -vE '^\s*(#|$)' /var/snap/authd-msentraid/current/broker.conf
[oidc]
issuer = https://login.microsoftonline.com/<TENANT_ID>/v2.0
client_id = aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
[msentraid]
[users]
ssh_allowed_suffixes_first_auth = @example.com
allowed_users = OWNER,alice@example.com
extra_groups = users
owner_extra_groups = sudo,lpadmin
[flows]
entra_auth = false
Public-key SSH and other security points
Read this section before putting authd on a server that is reachable from outside.
1. SSH public keys do not go through authd. Authentication with ~/.ssh/authorized_keys is handled by sshd alone. A person who was disabled in Entra, or whose authd account is locked, can still log in with a key that is in their home directory. Offboarding (see above) must remove the key, or switch off public keys for authd users. This sshd block does the second; it matches by the @domain of the user name. The check below asks sshd what it would do for an Entra user and for a local one:
$ cat /etc/ssh/sshd_config.d/authd-nopubkey.conf
Match User *@example.com
PubkeyAuthentication no
$ sshd -T -C user=alice@example.com,host=x,addr=127.0.0.1 | grep -i '^pubkeyauthentication'
pubkeyauthentication no
$ sshd -T -C user=bob,host=x,addr=127.0.0.1 | grep -i '^pubkeyauthentication'
pubkeyauthentication yes
$ rm /etc/ssh/sshd_config.d/authd-nopubkey.conf
2. The same Match mechanism limits the PAM prompts to authd users, so the rest of the server does not accept password-style logins. This is the form the cloud-init recipe uses (UsePAM yes outside, KbdInteractiveAuthentication only for the company's users):
$ cat /etc/ssh/sshd_config.d/authd.conf
UsePAM yes
Match User *@example.com
KbdInteractiveAuthentication yes
$ sshd -T -C user=alice@example.com,host=x,addr=127.0.0.1 | grep -i '^kbdinteractive'
kbdinteractiveauthentication yes
$ sshd -T -C user=bob,host=x,addr=127.0.0.1 | grep -i '^kbdinteractive'
kbdinteractiveauthentication no
Only the company's users get the PAM prompts; the local user bob does not, so the rest of the machine keeps its stricter settings.
3. Offline logins. When Entra cannot be reached, authd lets a returning user in with the local password, even if the account has been disabled in Entra since. force_access_check_with_provider = true closes that gap and makes every login need Entra, so an outage means nobody can log in. Pick one on purpose, and keep a local administrator account for emergencies (or know how to reach recovery mode: hold the right Shift key at boot, choose the advanced options and the recovery entry, then the root shell).
4. Cached secrets. The OAuth refresh token and the user information from Entra are cached in plain text, so that the local password can work offline, under /var/snap/authd-msentraid/current/, in a directory that only root can read (mode 0700). Use full disk encryption on laptops, so a lost machine does not give the tokens away. (Documented; the lab has no cached login to look at.)
5. Local passwords. The local password is stored only as a salted Argon2id hash, and its strength is checked with libpwquality. The rules can be changed with a drop-in file:
$ mkdir -p /etc/security/pwquality.conf.d
$ echo 'minlen = 14' > /etc/security/pwquality.conf.d/99_custom-options.conf
$ cat /etc/security/pwquality.conf.d/99_custom-options.conf
minlen = 14
This applies only to the local password. A password the user types for Entra itself is checked by the tenant's password policy, and authd stores a hash of it for offline use WITHOUT applying the local rules. Set the tenant policy, not this file, to stop weak Entra passwords.
6. Keep the authd package updated (snaps update by themselves, the deb does not unless you enable unattended upgrades). Whoever can administer the Entra tenant (and the linux-* groups) can grant access and sudo on every machine, so protect those accounts the most. authd and the broker are sandboxed, but the project says plainly that a bug in authd could still lead to root.
Many machines: cloud-init and a script
For more than a few machines the steps are scripted. This is the recipe from Canonical's documentation for the Entra broker (cloud-init user data, with the tenant and client IDs as variables). Its shell lines were run in the lab, in the earlier steps and below.
## template: jinja
#cloud-config
{% set ISSUER_ID = '<your_issuer_id>' %}
{% set CLIENT_ID = '<your_client_id>' %}
apt:
sources:
source1:
source: 'ppa:ubuntu-enterprise-desktop/authd' # 24.04 only
packages:
- authd
snap:
commands:
- ['install', 'authd-msentraid']
write_files:
- path: /etc/ssh/sshd_config.d/authd.conf
content: |
UsePAM yes
Match User *@example.onmicrosoft.com
KbdInteractiveAuthentication yes
runcmd:
- apt-get upgrade -y
- sed -i 's|<CLIENT_ID>|{{ CLIENT_ID }}|g; s|<ISSUER_ID>|{{ ISSUER_ID }}|g' /var/snap/authd-msentraid/current/broker.conf
- echo 'ssh_allowed_suffixes_first_auth = @example.onmicrosoft.com' >> /var/snap/authd-msentraid/current/broker.conf
- sed -i 's/^\(LOGIN_TIMEOUT\t\t\)[0-9]\+/\1360/' /etc/login.defs
- mkdir -p /etc/authd/brokers.d/
- cp /snap/authd-msentraid/current/conf/authd/msentraid.conf /etc/authd/brokers.d/
- snap restart authd-msentraid
- systemctl restart authd ssh
The cloud-init file itself was not run (the lab is not a cloud image). Its commands were run one by one instead. This is the login timeout change (60 seconds to 360 seconds), applied with the recipe's own sed:
$ grep -n '^LOGIN_TIMEOUT' /etc/login.defs
206:LOGIN_TIMEOUT 60
$ sed -i 's/^\(LOGIN_TIMEOUT\t\t\)[0-9]\+/\1360/' /etc/login.defs
$ grep -n '^LOGIN_TIMEOUT' /etc/login.defs
206:LOGIN_TIMEOUT 360
And the restart of both services in one command, as in the last two lines of the recipe:
$ snap restart authd-msentraid
Restarted.
$ systemctl restart authd ssh; systemctl is-active authd ssh
active
active
For machines you manage with Landscape, Canonical has a matching deployment script and a blog post; for Ansible, the same lines translate directly (apt_repository, snap, lineinfile, copy, service). Keep the client ID and tenant ID in your variables, and never put a client secret into a repository (it belongs in the vault or secrets store of your tool).
Troubleshooting by symptom
What you see What it means and what to do
"Error connecting to provider. Check your network connection." Often NOT the network. Read the broker journal. A wrong tenant ID gives AADSTS90002 "Tenant ... not found" (seen in the lab). If the journal shows a connection failure instead, check HTTPS to login.microsoftonline.com.
"could not generate device code flow layout: ... AADSTS700016" The tenant exists but the client ID does not (seen in the lab). Copy the Application (client) ID again from the app registration.
Provider menu never appears over SSH, only "Password:" First SSH login is not allowed for this user name. Set ssh_allowed_suffixes_first_auth in the broker file (seen in the lab), and check UsePAM and KbdInteractiveAuthentication.
Broker service "failed" after editing broker.conf The broker checks its file at start. The journal says which rule (unedited placeholder, no flows enabled, entra_auth without register_device or client_secret: all three seen in the lab). After several failures systemd says "Start request repeated too quickly" (seen too): fix the file, run systemctl reset-failed on the unit, restart.
authd says "No broker configuration found, using only the local broker" The broker file is not in /etc/authd/brokers.d/, or authd was not restarted after copying it.
Only the first person can log in Expected: the first user is the owner and the only one allowed. Edit allowed_users (Step 4).
Login times out while the user is fetching the code LOGIN_TIMEOUT in /etc/login.defs is 60 seconds. Raise it (the cloud-init recipe uses 360).
A user disabled in Entra can still log in Either public-key SSH (authd is not involved), or offline login with the local password. See the security section: Match User with PubkeyAuthentication no, and force_access_check_with_provider.
Files owned by the wrong user over NFS or Samba authd's UIDs and GIDs are different on every machine. The documentation's fix is ID mapping: NFS4 with Kerberos and idmapd, or Samba with idmap settings (not run here).
Nobody can log in at all, after an upgrade or a removed snap Use a local administrator, or boot to recovery mode (hold right Shift at boot, advanced options, recovery, root shell) and repair or reinstall authd and the broker.
"Entra user shows in getent only after login" Normal: users are created at their first successful login.
To test the repair of a broken setup without waiting for a person, the quickest loop is: edit broker.conf, restart the broker, check "is-active", and try the ssh login in a second terminal while reading the journal with -f in the first:
$ sudo journalctl -f -u authd.service -u 'snap.authd-*.service'
Switching to the edge channel to test a fix is documented ("Changing authd versions"); not run here, and not for production machines.
Cheat sheet
INSTALL (24.04)
sudo add-apt-repository ppa:ubuntu-enterprise-desktop/authd
sudo apt install authd (Desktop: authd gnome-shell yaru-theme-gnome-shell)
sudo snap install authd-msentraid
sudo cp /snap/authd-msentraid/current/conf/authd/msentraid.conf /etc/authd/brokers.d/
CONFIGURE
sudoedit /var/snap/authd-msentraid/current/broker.conf (issuer, client_id, [users], [flows], [msentraid])
sudo systemctl restart authd
sudo systemctl restart snap.authd-msentraid.authd-msentraid.service (26.04+: add -v)
issuer = https://login.microsoftonline.com/<ISSUER_ID>/v2.0
client_id = <CLIENT_ID>
allowed_users = OWNER | ALL | name,name owner = ... extra_groups = ... owner_extra_groups = ...
register_device = true [msentraid] entra_auth = true [flows]
force_access_check_with_provider = true [oidc]
ssh_allowed_suffixes_first_auth = @example.com [users]
SSH
/etc/ssh/sshd_config.d/authd.conf: UsePAM yes + KbdInteractiveAuthentication yes
ssh user@example.com@host
USERS AND GROUPS
getent passwd user@example.com id user@example.com
Entra group linux-sudo -> Linux group sudo
sudo authctl user lock|unlock|delete user@example.com sudo authctl user delete --remove-home user@example.com
sudo loginctl terminate-user user@example.com
sudo authctl user set-uid|set-shell|set-home ... sudo authctl group set-gid|delete ...
LOGS
sudo journalctl -u authd.service -u "snap.authd-*.service"
sudo sed -i '/pam_authd_exec\.so\|pam_authd\.so/ s/$/ debug=true/' /etc/pam.d/*
AUTHD_NSS_INFO=stderr getent passwd user@example.com
systemd drop-in: ExecStart= then ExecStart=/usr/libexec/authd -vv
broker drop-in: ExecStart= then ExecStart=/usr/bin/snap run authd-msentraid -vv
FILES
/etc/authd/authd.yaml verbosity, UID and GID ranges
/etc/authd/brokers.d/ broker descriptions
/var/snap/authd-msentraid/current/broker.conf and broker.conf.d/
/var/lib/authd/authd.sqlite3 user database. Do not delete.
/etc/login.defs LOGIN_TIMEOUT
Common problems and tips
- Test with a throw-away virtual machine first, and keep a local administrator account on every machine until you trust the setup.
- Copy the IDs from the app registration with the copy button. A mistyped character gives one of the two AADSTS errors above.
- After every change to broker.conf: restart the broker, check "is-active", read the journal. A silent broker failure looks to the user like "Error connecting to provider".
- Choose allowed_users on purpose. The default (first person is the owner and the only user) is safe, but surprising.
- Give sudo through an Entra group named linux-sudo, not by hand on each machine, and watch who can edit it.
- Decide about offline logins (local password) and force_access_check_with_provider before the first user is rolled out.
- Switch off public-key SSH for the company's users, or remove the keys when someone leaves.
- Passwordless and device code both work from a phone. For staff without a phone available, plan another way (a Temporary Access Pass issued by IT).
- NFS and Samba need ID mapping because the numeric IDs differ from machine to machine.
- This guide was tested on Ubuntu 24.04 with authd 0.7.1 and the broker 0.5.4. Newer releases (26.04 puts authd in the archive; press coverage of 26.10 mentions stronger Entra ID login, but this guide could not confirm the details from Canonical's own release notes) were not tested: read the release notes and Canonical's authd documentation (documentation.ubuntu.com/authd) for what changed, and re-test the sign-in screen after each upgrade.