Xpra’s authentication modules can be useful for:
For more information on the different types of connections, see network. For more generic security information, please see security considerations
SSL mode can also be used for authentication using certificates (see #1252)
When using SSH to connect to a server, encryption and authentication can be skipped: by default the unix domain sockets used by ssh do not use authentication.
Starting with version 6.5, options for individual authentication modules are specified using brackets:
auth=MODULE(option=value,...).
ie for starting a seamless server with a TCP socket protected by a password stored in a file:
xpra seamless --start=xterm -d auth
--bind-tcp=0.0.0.0:10000,auth=file(filename=password.txt)
Multiple sockets can use different authentication modules, and those modules can more easily be chained:
xpra seamless --start=xterm -d auth \
--bind-tcp=0.0.0.0:10000,auth=hosts,auth=file(filename=password.txt) \
--bind-tcp=0.0.0.0:10001,auth=sys
This is the recommended syntax, and the only one that is unambiguous:
, and = characters,
which is common for command lines, paths and urisThe older auth=MODULE:option=value and auth=MODULE,option=value forms are still accepted.
The latter makes the option a socket option, which is then given to every authentication module
used by that socket - so it cannot be used to give different values to two chained modules.
Xpra supports many authentication modules. Some of these modules require extra dependencies.
| Module | Result | Purpose |
|————————————————————————————————–|—————————————————————————————–|————————————————————————————-|
| allow | always allows the user to login, the username used is the one supplied by the client | dangerous / only for testing |
| none | always allows the user to login, the username used is the one the server is running as | dangerous / only for testing |
| fail | always fails authentication, no password required | useful for testing |
| reject | always fails authentication, pretends to ask for a password | useful for testing |
| env | matches against an environment variable (XPRA_PASSWORD by default) | alternative to file module |
| password | matches against a password given as a module option, ie: auth=password(value=mysecret) | alternative to file module |
| multifile | matches usernames and passwords against an authentication file | proxy: see password-file below |
| file | compares the password against the contents of a password file, see password-file below | simple password authentication |
| scram | SCRAM authentication using python-scramp | supports plaintext files and SCRAM stored-key records |
| pam | linux PAM authentication | Linux system authentication |
| win32 | win32security authentication | MS Windows system authentication |
| sys | system authentication | virtual module which will choose win32 or pam authentication automatically |
| sqlite | sqlite database authentication | #1488 |
| sql | sqlalchemy database authentication | #2288 |
| mysql | MySQL database authentication | #2287 |
| capability | matches values in the capabilities supplied by the client | #3575 |
| peercred | SO_PEERCRED authentication | #1524 |
| hosts | TCP Wrapper | #1730 |
| ratelimit | delays then rejects clients that keep failing to authenticate | brute force protection, chain it before a real authentication module |
| exec | Delegates to an external command | #1690 |
| kerberos-password | Uses kerberos to authenticate a username + password | #1691 |
| kerberos-token | Uses a kerberos ticket to authenticate a client | #1691 |
| gss | Uses a GSS ticket to authenticate a client | #1691 |
| oauth | Uses an OAuth2 Bearer token from websocket HTTP headers or client capabilities | validates a static token or token introspection endpoint |
| keycloak | Uses a keycloak token to authenticate a client | #3334 |
| ldap | Uses ldap via python-ldap | #1791 |
| ldap3 | Uses ldap via python-ldap3 | #1791 |
| u2f | Universal 2nd Factor | #1789 |
| fido2 | FIDO Alliance | #1789 |
| otp | One Time Password | pyotp |
| otpscreen | Generates a one-time secret and shows it in a local GUI dialog for the user to type | local secondary-channel confirmation (distinct from otp) |
| email | Sends a short-lived verification code by SMTP | fixed, prompted, SQL/command lookup, or username-derived destination |
| sms | Sends a short-lived verification code by SMS, through an HTTP API | fixed, prompted, or SQL/command lookup phone number; any provider (ie: Brevo) |
| notify | Asks the current user(s) of the session to allow the connection, using a notification | shadow servers: the desktop user, seamless servers: the connected clients, MS Windows: tray balloon |
| http-header | validate websocket http headers | #4438 |
XPRA_PASSWORD=mysecret xpra seamless --bind-tcp=0.0.0.0:10000,auth=envSOME_OTHER_ENV_VAR_NAME=mysecret xpra seamless --bind-tcp=0.0.0.0:10000,auth=env(name=SOME_OTHER_ENV_VAR_NAME)xpra seamless --bind-tcp=0.0.0.0:10000,auth=password(value=mysecret)xpra seamless --bind-tcp=0.0.0.0:10000,auth=file(filename=/path/to/mypasswordfile.txt)xpra seamless --bind-tcp=0.0.0.0:10000,auth=sqlite(filename=/path/to/userlist.sdb)xpra seamless --bind-tcp=0.0.0.0:10000,auth=otpscreen(mode=alphanumeric,count=8,timeout=60)xpra shadow --bind-tcp=0.0.0.0:10000,auth=notify(timeout=20)Beware when mixing environment variables and password files as the latter may contain a trailing newline character whereas the former often do not.
The otpscreen module accepts the following options: mode (digits, alpha or alphanumeric, default digits), count (number of characters in the generated secret, default 6), timeout (how long the dialog stays up, in seconds, default 120), and display (which display to open the dialog on, default auto which reuses the server’s saved DISPLAY / WAYLAND_DISPLAY).
The notify module asks the current user(s) of the session to allow the connection, and waits for an answer.
On Linux and other dbus platforms, it sends a notification with Allow and Deny actions to the notification service on the server’s session bus.
With shadow servers, this is the notification service of the desktop being shadowed, so the user sitting in front of it is asked.
With seamless servers, this is the server’s own notification forwarder, so the request is shown by the clients currently connected (if they have notifications enabled).
The connection is rejected if the request is denied or dismissed, or if the notification service does not support actions.
On MS Windows, the request is shown as a balloon notification on the server’s system tray icon, and clicking it opens a confirmation dialog.
The connection is rejected if there is no system tray icon to attach the notification to, if another request is already pending, or if the dialog is answered No.
On all platforms, the connection is also rejected if nobody answers within timeout seconds (default 30).
The email module sends a one-time code by SMTP and asks the client to enter it.
Chain it after primary authentication to require both checks. For example, using an existing TLS certificate:
xpra seamless --start=xterm \
--bind-ssl='0.0.0.0:14500,auth=ratelimit,auth=pam,auth=email(mode=fixed,address=alice@example.com,smtp-host=smtp.example.com,from=xpra@example.com)' \
--ssl-cert=/path/to/server-cert.pem --ssl-key=/path/to/server-key.pem
xpra attach ssl://server.example.com:14500/ --challenge-handlers=prompt,prompt
Use TLS, SSH, or another protected Xpra connection: an HMAC challenge does not encrypt the connection, and a short numeric code can be guessed offline from a captured exchange.
Address selection is explicit; invalid configuration or a failed lookup rejects authentication without a fallback:
| Options | Destination |
|---|---|
mode=fixed,address=alice@example.com |
A preconfigured mailbox; fixed is the default mode. |
mode=prompt |
Ask for a complete email address, then send the code. |
mode=prompt,address=@example.com |
Ask only for the local part and append @example.com; \@example.com is also accepted. |
mode=lookup,lookup=sql,uri=sqlite:////path/to/users.db |
Look up the login username in a database. |
mode=lookup,lookup=exec,command=/usr/local/bin/xpra-email-lookup |
Run a helper to look up the login username. |
mode=derived,address=@example.com |
Append the domain to the login username without asking for an address. |
Prompt mode accepts the entered mailbox without matching it to the login account. It proves access to that mailbox; use fixed, lookup, or derived addresses when the destination must be administrator controlled. One ASCII mailbox is accepted. Address lists, display names, quoted local parts, whitespace, and control characters are rejected. Internationalized domains can be configured using their ASCII IDNA form; SMTPUTF8 local parts are not supported.
For prompt mode after a password check, configure three client handlers:
xpra attach ssl://server.example.com:14500/ --challenge-handlers=prompt,prompt,prompt
These answer the password, address, and code challenges in order, using the existing password dialogs, which hide typed text.
Current Python clients register four prompt handlers by default, but explicit configuration also works with older clients
that support successive challenges. Saved URI, file, or environment passwords are tried according to the configured handler order;
use explicit handlers to prevent an unused saved password from being submitted as an email address or code.
Address collection requires the client’s xor digest; code verification requires an HMAC digest.
SMTP configuration:
| Option | Default | Purpose |
|---|---|---|
smtp-host |
required | SMTP relay hostname. |
from |
required | Sender mailbox, also used as the SMTP envelope sender. |
smtp-security |
starttls |
Required STARTTLS, implicit tls, or explicitly selected plain for a trusted relay. |
smtp-port |
587 / 465 / 25 |
Default depends on smtp-security. |
smtp-timeout |
10 |
Timeout in seconds for SMTP socket operations. |
smtp-username |
empty | Optional SMTP login; requires smtp-password-file. |
smtp-password-file |
empty | UTF-8 password file, resolved relative to the server’s startup directory. One trailing line ending is removed. |
digits |
6 |
Numeric code length, from 6 to 12 digits, including leading zeroes. |
timeout |
120 |
Code lifetime in seconds, starting after the relay accepts the message. |
By default, TLS verifies the relay certificate and hostname using the system trust store and never falls back to plaintext.
For an authenticated relay, append smtp-username=sender,smtp-password-file=/path/to/smtp-password to the module options.
Restrict access to the password file. SMTP acceptance does not guarantee inbox delivery.
Configure SMTP TLS through the following auth module options, which apply to both smtp-security=tls and starttls:
| Option | Default | Purpose |
|---|---|---|
smtp-ssl-ca-certs |
system trust | Additional trusted CA or self-signed certificate in a PEM file, or an OpenSSL hashed CA directory. default keeps system trust. |
smtp-ssl-ca-data |
empty | Additional trusted certificate data: PEM text, or DER encoded as hex or base64. |
smtp-ssl-cert |
empty | Client certificate chain in PEM format; it may also contain the private key. |
smtp-ssl-key |
empty | Separate client private key file; requires smtp-ssl-cert. |
smtp-ssl-key-password-file |
empty | UTF-8 password file for an encrypted client key; requires smtp-ssl-cert. One trailing line ending is removed. |
smtp-ssl-server-verify-mode |
required |
Certificate verification: required, optional (equivalent to required for a TLS client), or none. |
smtp-ssl-check-hostname |
yes |
Match the relay certificate against smtp-host. Defaults to no if verification is explicitly set to none. |
smtp-ssl-protocol |
TLS |
Protocol name accepted by Xpra’s SSL parser, such as TLS, TLS_CLIENT, or TLSv1_2. |
smtp-ssl-ciphers |
Python defaults | OpenSSL cipher selection string for TLS 1.2 and earlier; TLS 1.3 cipher suites are unaffected. |
smtp-ssl-verify-flags |
Python defaults | Replace certificate verification flags, using names such as X509_STRICT or X509_PARTIAL_CHAIN. |
smtp-ssl-options |
Python defaults | Add OpenSSL option flags, such as NO_COMPRESSION or NO_RENEGOTIATION, retaining the default options. |
These settings affect only the SMTP connection. SSL options on the listening socket and global Xpra SSL options do not configure SMTP.
Certificate, key, and password file paths are resolved relative to the server’s startup directory.
For lists of verification or option flags inside email(...), use + as the separator, for example
smtp-ssl-verify-flags=X509_STRICT+X509_PARTIAL_CHAIN; commas would separate auth module options.
Unknown SMTP SSL options and SSL options combined with smtp-security=plain are rejected.
To trust a self-signed relay certificate while retaining certificate and hostname verification, add its public certificate:
email(address=alice@example.com,smtp-host=mail.example.com,from=xpra@example.com,smtp-security=tls,smtp-ssl-ca-certs=/path/to/smtp-cert.pem)
For a relay signed by a private CA, use the CA certificate instead. The relay certificate must match smtp-host.
This loads trust for the SMTP connection without changing the system trust store or setting SSL_CERT_FILE for the whole process.
smtp-ssl-check-hostname=no disables only hostname matching; certificate verification remains enabled.
smtp-ssl-server-verify-mode=none disables certificate verification and hostname matching; combining it with explicit
smtp-ssl-check-hostname=yes is rejected. Client certificates do not replace SMTP username/password login if the relay requires it.
SQL lookup requires optional SQLAlchemy and the database’s driver. query defaults to
SELECT email FROM users WHERE username=:username; the username is a bound parameter, never interpolated SQL.
The query must return exactly one row with one nonempty address. The module neither creates nor migrates tables.
Configure connection/query timeouts through the database driver, and use an account with read-only access.
For example, a SQLite database can contain users(username TEXT PRIMARY KEY, email TEXT NOT NULL).
Exec lookup splits command into arguments without using a shell, then appends the login username as one argument.
The helper must exit successfully and write exactly one UTF-8 address, optionally followed by one line ending, to stdout.
Output is limited to 4 KiB; stderr is discarded. lookup-timeout=10 limits reading and waiting for the helper, which is killed
and reaped on timeout. Use an absolute executable path and quote arguments within the command as needed. For example:
#!/usr/bin/env python3
import sys
addresses = {"alice": "alice@example.com", "bob": "bob@example.net"}
address = addresses.get(sys.argv[1])
if not address:
raise SystemExit(1)
print(address)
There is one code attempt per connection. Incorrect or expired codes disconnect the client; reconnecting requests a fresh code.
The existing server challenge deadline (normally 120 seconds) and client dialog timeout can shorten the usable lifetime;
increasing timeout alone does not extend those deadlines. Disconnecting invalidates pending challenges.
This module does not supply proxy session routing; retain an authentication module that authorizes the target account/session.
Send attempts are limited to one per mailbox per 60 seconds and five per peer IP per 300 seconds, including failed deliveries.
Limits are shared across connections and sockets within one server process, include loopback IPs, and reset on restart.
At most 10,000 mailbox/IP keys are retained; expired entries are pruned, and new keys are rejected when storage is full.
These send limits complement the ratelimit module, which tracks authentication failures.
The sms module sends a one-time code by SMS and asks the client to enter it.
It shares the challenge flow, code rules, timeouts, lookups and send limits of the email module:
chain it after primary authentication, over a protected connection, and see the email verification section above for the details.
The code is delivered by an HTTPS request to the SMS provider’s API, which is described using generic options
so that any provider can be used. For example, with Brevo:
xpra seamless --start=xterm \
--bind-ssl='0.0.0.0:14500,auth=ratelimit,auth=pam,auth=sms(number=+33680065433,url=https://api.brevo.com/v3/transactionalSMS/send,headers-file=/etc/xpra/brevo-headers,from=Xpra)' \
--ssl-cert=/path/to/server-cert.pem --ssl-key=/path/to/server-key.pem
xpra attach ssl://server.example.com:14500/ --challenge-handlers=prompt,prompt
With /etc/xpra/brevo-headers containing the API key, readable only by the server:
api-key: xkeysib-...
Phone number selection:
| Options | Destination |
|---|---|
mode=fixed,number=+33680065433 |
A preconfigured number; fixed is the default mode. |
mode=prompt |
Ask for a number in international format: +33 6 80 06 54 33 or 0033680065433. |
mode=prompt,country=+33 |
Also accept national numbers: one leading 0 is removed and the country code is prepended. |
mode=lookup,lookup=sql,uri=sqlite:////path/to/users.db |
Look up the login username in a database, the default query is SELECT phone FROM users WHERE username=:username. |
mode=lookup,lookup=exec,command=/usr/local/bin/xpra-phone-lookup |
Run a helper to look up the login username. |
Numbers are normalized to E.164: spaces, dashes, dots and parentheses are removed,
a leading 00 is replaced with +, and the result must be + followed by 7 to 15 digits.
As with email, prompt mode only proves that the user can receive messages on the number they enter.
HTTP request options:
| Option | Default | Purpose |
|---|---|---|
url |
required | Provider endpoint. Must use https, unless http-insecure=yes is set, ie: for a local gateway. Credentials are not allowed in the URL. |
method |
POST |
POST, PUT or GET. GET requests have no body: use placeholders in the url query string instead. |
body-format |
json |
json or form: sets the Content-Type and how values are escaped. |
body-file |
Brevo JSON body | Request body template, required for form. The default is {"sender": "{from}", "recipient": "{to_digits}", "content": "{message}"}. |
headers-file |
empty | Extra request headers, one Name: value per line; # starts a comment. Re-read for every message, so keys can be rotated. |
from |
empty | Sender name or number, up to 15 letters, digits, spaces, ., + or -. Providers may restrict this further. |
message |
Your Xpra verification code is {code} |
Message text, which must include {code} and not exceed 160 characters. Commas would separate auth module options. |
http-timeout |
10 |
Timeout in seconds for the HTTP request. |
http-ssl-* |
system trust | Same options as the email module’s smtp-ssl-* options, ie: http-ssl-ca-certs=/path/to/ca.pem. |
These placeholders can be used in the url and body templates:
| Placeholder | Value |
|---|---|
{to} |
Phone number in E.164 format, ie: +33680065433 |
{to_digits} |
The same number without the +, ie: 33680065433 |
{from} |
The from option |
{message} |
The rendered message text |
{code} |
The verification code alone |
Values are escaped for where they are inserted: URL encoding in the url, JSON string escaping in json bodies
(so placeholders belong inside JSON strings), and form encoding in form bodies.
A placeholder in a value is never expanded again. The templates are rendered at startup to catch invalid JSON.
Any 2xx response status is accepted. Error statuses, timeouts and connection failures reject authentication
and are not shown to the client. Redirects are never followed, as urllib would send the headers, including API keys,
to the new location. The usual https_proxy environment variables are honoured.
Keep API keys in headers-file, not on the command line, and restrict access to that file.
For a provider which uses form encoding and basic authentication, such as Twilio:
sms(mode=prompt,url=https://api.twilio.com/2010-04-01/Accounts/ACXXX/Messages.json,body-format=form,body-file=/etc/xpra/sms-body,headers-file=/etc/xpra/sms-headers,from=+15550100199)
With /etc/xpra/sms-body containing To={to}&From={from}&Body={message},
and /etc/xpra/sms-headers containing Authorization: Basic <base64 of account-sid:auth-token>.
The ratelimit module protects a socket against brute force attacks: it records how many times each client IP address has recently failed to authenticate, delays the ones that keep failing, and eventually rejects them outright.
It does not authenticate anyone by itself - it is a gate that must be chained before a real authentication module, and it must be listed first so that a blocked address is turned away before the server even sends it a challenge:
xpra start --bind-tcp=0.0.0.0:10000 \
--tcp-auth=ratelimit(max-failures=3,window=60,ipv6-prefix=64) \
--tcp-auth=password(value=mysecret)
| Option | Default | Purpose |
|---|---|---|
max-failures |
5 |
how many failures within the window are allowed before the client is rejected |
window |
60 |
how long a failure is remembered, in seconds |
delay |
1 |
delay added after the first failure, doubling with each one; 0 disables the delay |
max-delay |
8 |
upper limit for that delay, in seconds |
ipv4-prefix |
32 |
group IPv4 addresses by prefix, ie: 24 counts a whole /24 together |
ipv6-prefix |
128 |
group IPv6 addresses by prefix - 64 is recommended, see below |
max-tracked |
10000 |
how many addresses to remember at most |
Once max-failures is reached, the client is rejected until the window expires: the rejected attempts are not counted again, so a legitimate user who gets locked out always recovers after window seconds.
An attacker usually controls an entire IPv6 subnet, so limiting each individual IPv6 address (the default) is easily bypassed by picking a new one for each attempt - use ipv6-prefix=64 to count a whole /64 together.
Loopback addresses, unix domain sockets and named pipes are never rate limited.
The syntax with older versions used a dedicated switch for each socket type:
--auth=MODULE for unix domain sockets and named pipes--tcp-auth=MODULE for TCP sockets--vsock-auth=MODULE for vsock (#983)
etcFor more information on the different socket types, see network examples
By default, challenge-handlers=all which means that the python client will try all authentication handlers available until one succeeds.
If the server is configured with multiple authentications modules for the same socket, the client will do the same.
Authenticating as username foo with password bar using the URI:
xpra attach tcp://foo:bar@host:port/
For a more secure option, storing the password value in a file, with debugging enabled:
echo -n "foo" > ./password.txt
xpra attach tcp://host:port/ --challenge-handlers=file:filename=./password.txt --debug auth
| Module | Behaviour and options |
|————————————————————————————-|———————————————————————————————————-|
| env | name specifies the environment variable containing the password
defaults to XPRA_PASSWORD |
| file | filename specifies the file containing the passowrd |
| scram | SCRAM password proof handler using python-scramp; legacy-sha1=yes enables SCRAM-SHA-1 |
| gss | use gss-services to specify the name of the security context |
| kerberos | kerberos-services specifies the valid kerberos services to connect to
the wildcard * may be used |
| prompt | GUI clients should see a dialog, console users a text prompt |
| u2f | APP_ID specifies the u2f authentication application ID |
| fido2 | APP_ID specifies the FIDO2 authentication application ID |
| uri | Uses values parsed from the connection string, ie: tcp://foo:bar@host |
file module, the password-file contains a single password, the whole file is the password (including any trailing newline characters). To write a password to a file without the trailing newline character, you can use echo -n "thepassword" > password.txtmultifile, the password-file contains a list of authentication values, see proxy server - this module is deprecated in favour of the sqlite module which is much easier to configureThe username can be specified:
xpra attach tcp://username:password@host:port/
When an authentication module is used to secure a single session, many modules will completely ignore the username part, and it can be omitted from the connection string. This can be overriden for some modules.
for connecting to the TCP socket and specifying the password only:
xpra attach tcp://:password@host:port/
Since the username is ignored, it can also be replaced with any string of your liking, ie using foobar here:
xpra attach tcp://foobar:password@host:port/
Only the following modules will make use of both the username and password to authenticate against their respective backend: kerberos-password, ldap, ldap3, sys (pam and win32), sqlite, sql, mysql, multifile and u2f.
In this case, using an invalid username will cause the authentication to fail.
The username is usually more relevant when authenticating against a proxy server (see authentication details there).
The proxy server needs more than a yes/no answer from authentication: it also needs to know which xpra sessions the authenticated client may reach, and as which uid/gid to spawn (or connect to) the proxy instance.
Today, that lookup is bundled into the authentication module via the get_sessions() method on SysAuthenticatorBase, which returns a 5-tuple:
(uid, gid, displays, env_options, session_options)
uid, gid: the system identity the proxy instance runs asdisplays: the list of display names the user may attach to (e.g. [":10", ":11"])env_options: extra environment variables applied to the proxy instance processsession_options: extra session-level options passed to the proxy instanceThe proxy server iterates over the protocol’s authenticator chain after the challenge passes and uses the first non-empty result (see xpra/server/proxy/server.py).
The default implementation in SysAuthenticatorBase.get_sessions() performs a DotXpra socket-directory scan for the authenticated uid, listing every live xpra socket owned by the user. Most modules use this default (pam, ldap, ldap3, password, peercred, keycloak, kerberos-*, gss, u2f, fido2, otp, otpscreen, notify, capability, env, exec, hosts, http-header, allow, none, win32, file).
Three families override it to return data they already store per user:
| Module | Source of session data |
|---|---|
multifile |
Extra columns in the password file (see the multifile format in Proxy-Server.md) |
sqlite |
Columns uid, gid, displays, env_options, session_options of the users table (see xpra/auth/sqlauthbase.py schema) |
sql |
Same schema, via SQLAlchemy |
mysql |
Same schema, against MySQL |
The fail and reject modules deny authentication outright and therefore never reach session lookup.
--session-registry proxy optionThe proxy server lets you pick the session registry independently of the authenticator with --session-registry=NAME[(opt=val,...)] (default auth):
| Registry | Behaviour |
|---|---|
auth |
Delegates to authenticator.get_sessions() — the historical behaviour. multifile/sql* setups need no changes. |
socket |
Performs a DotXpra socket-directory scan for the authenticated uid — pairs any authenticator with socket discovery. |
multifile |
Reads username\|password\|uid\|gid\|displays\|env\|session_options from a file (filename option). Lookup is by username. |
sqlite |
Looks up (uid, gid, displays, env_options, session_options) from the users table of an sqlite database (filename option). |
sql |
Same schema, via SQLAlchemy (uri option). |
mysql |
Same schema, against MySQL (uri option). |
live |
Runtime map of sessions populated by xpra servers that dial out to the proxy at startup with --register=URI. See below. |
Example: use pam to authenticate but read the per-user session mapping from an sqlite file:
xpra proxy --bind-tcp=0.0.0.0:14500,auth=pam --session-registry=sqlite(filename=/etc/xpra/users.sdb)
Registry modules live under xpra/server/session_registry/.
live backend and --registerA server can announce itself to a proxy at startup with the --register=URI option (repeatable). For each URI the server dials the proxy, authenticates as a client, and sends a hello packet carrying request=register along with its uuid, session-name and display.
# proxy side:
xpra proxy --bind-tcp=0.0.0.0:14500 --session-registry=live --auth=password(value=secret)
# server side (--session-name names the registered session):
xpra seamless --start=xterm --session-name=demo --register=tcp://:secret@proxy.example.com:14500/
The proxy exposes the registered sessions under the registered key in xpra info.
To address a specific registered session, pass --display=NAME to xpra attach:
xpra attach tcp://proxy.example.com:14500/ --display=demo
When exactly one session is registered, xpra attach tcp://proxy.example.com:14500/ is enough — the proxy auto-selects it.
By default the name supplied by --display is matched against each registered session’s session-name (and then its registered displays). The match policy can be changed on the proxy with the lookup-by option (session-name, uuid or display).
The proxy never dials out — it only ever accepts inbound connections, which makes it usable in front of NAT-ed servers. After each client is brokered, the server re-registers automatically so the slot stays warm for the next one.
The steps below assume that the client and server have been configured to use authentication:
challenge-handlers option, by default the client will try the following handlers in the specified order: uri (whatever password may have been specified in the connection string), file (if the password-file option was used), env (if the environment variable is present), scram, kerberos, gss, keycloak, u2f and finally promptxpra infopam, win32, kerberos-password, ldap and ldap3) require the actual password to be sent across to perform the authentication on the server - they therefore use the weak xor hashing, which is insecurexor hashing so that the password is protected during the exchange: the system will refuse to send a xor hashed password unencryptedauth debug logging may leak some authentication informationFor more information on packets, see network.
A new server-side authentication module is a Python file in xpra/auth/ that defines a class named Authenticator. Two base classes are provided:
SysAuthenticatorBase — the minimal base. Use this when the username does not need to map to a local system account (e.g. token-based or capability-based authenticators).SysAuthenticator (in the same file) — extends the base by loading the local pwd entry for self.username on POSIX. Use this when the module is tied to system users (pam, peercred, exec, etc.).The methods most commonly overridden:
| Method | Default | When to override |
|---|---|---|
requires_challenge() |
returns True |
Return False for modules that authenticate out of band (e.g. peercred, hosts, http-header). |
get_challenge(digests) |
Generates a salt and chooses the strongest compatible digest | Override when the module requires a named non-HMAC digest or custom challenge payload. |
get_next_challenge() |
returns () |
Override for multi-step challenge protocols. Return (challenge, digest, prompt) while another client response is needed. |
get_passwords() / get_password() |
get_passwords returns (get_password(),); get_password returns "" |
Override one of them to provide the expected password(s) — used by HMAC challenge verification. |
do_authenticate(caps) |
Validates the challenge response and calls authenticate_check |
Override for non-HMAC flows (e.g. challenge/response over a different transport, third-party token verification). |
authenticate_hmac(caps) |
Verifies the HMAC challenge against get_passwords() results |
Override if you need to perform extra checks after a successful HMAC match. |
get_uid() / get_gid() |
NotImplementedError |
Always override. Return the uid/gid the proxy instance should run as. Use parse_uid / parse_gid from common.py. |
get_sessions() |
Performs a DotXpra socket scan for the authenticated uid |
Leave alone unless your backend stores per-user session metadata (see multifile and sqlauthbase.py for examples). |
Helpers in xpra/auth/common.py:
SessionData — the (uid, gid, displays, env_options, session_options) 5-tuple returned by get_sessions()parse_uid(v) / parse_gid(v) — accept either a numeric string or a username/group name, with safe defaultsget_auth_exec_env(display="auto") — environment dictionary suitable for spawning helper processes (used by exec and otpscreen)Authenticator instances are constructed by auth_helper.get_auth_module(), which parses the auth=NAME(opt=value,...) syntax and imports xpra.auth.<name>. Each socket can chain multiple authenticators; the first one to require a challenge issues it and subsequent ones either verify additional caps or contribute to get_sessions().
Multi-step authenticators keep their state on the Authenticator instance. After authenticate(caps) succeeds, the server calls get_next_challenge(): return () when the authenticator is complete, or return (challenge_bytes, digest_name, prompt) to send another challenge packet. The next client response arrives in caps["challenge_response"] and is processed by the same authenticator.
Three optional callbacks are called on the authenticators of a connection, if they are defined:
| Callback | Called when |
|---|---|
auth_failed() |
any module in the chain rejected the client - an authenticator only ever sees its own result, this is how it can find out that a later module failed |
auth_succeeded() |
every module in the chain has passed |
cleanup() |
the authenticators are discarded (on success and on failure), to free up any resources |
A new Authenticator is instantiated for every connection, so a module that needs to remember something across connections (like ratelimit, which counts the failures of each client IP) must keep that state at the class level and protect it with a lock: verify_auth runs in a separate thread for each connection.
A new client-side challenge handler is a Python file in xpra/challenge/ that defines a class named Handler implementing AuthenticationHandler.
| Method | Default | When to override |
|---|---|---|
get_digests() |
abstract | Return the digest names handled by this module, or () for generic password handlers. |
handle(challenge, digest, prompt) |
abstract | Return the response bytes/value for the server challenge, or a false value if the handler cannot answer. |
is_done() |
returns True |
Return False for multi-step handlers that must keep state and handle the next challenge packet. |
For multi-step handlers, keep protocol state on the handler instance. When handle() returns a response and is_done() is False, the client keeps that handler at the front of the handler list so the next server challenge is routed back to it.
HMAC_* and we did get it wrong before…