TACACS+ Authentication, Authorization, and Accounting
How to configure TACACS+ in Radiator Server for device administration, including ASCII multistage flows with one-time password challenges.
TACACS+ Authentication, Authorization, and Accounting
Radiator Server implements TACACS+ as defined by RFC 8907. TACACS+ is the authentication, authorization, and accounting (AAA) protocol used by network devices (switches, routers, firewalls, load balancers) for administrator login and command authorization.
When to Use TACACS+
Use TACACS+ when you need to:
- Authenticate operators connecting to network devices (SSH, console, web management) and keep a short-lived built-in authorization session after login
- Authorize individual CLI commands per operator (per-command authorization)
- Record an audit trail of administrator actions (command and session accounting)
- Combine device login with multi-factor authentication
TACACS+ is separate from RADIUS. Use RADIUS for end-user network access (Wi-Fi, VPN, 802.1X) and TACACS+ for administrative access to the network devices themselves.
Transport Options
Radiator supports two TACACS+ transports on the listener:
| Transport | Default port | When to use |
|---|---|---|
| TCP | 49 | Devices on a trusted management network |
| TLS (TACACS+ over TLS) | 49 | Devices that cross untrusted networks or require certificate-based identity |
There is no IANA-assigned port for TACACS+ over TLS.
secret and secret none apply equally on both transports. Even on TLS, the TACACS+ packet body is still obfuscated (or not) per the configured secret mode for compatibility with TACACS+ clients.
PROXY protocol is supported on TCP listeners only. Combining tls and proxy-protocol on the same TACACS+ listener is rejected at configuration load time.
Authentication Types
TACACS+ defines four authentication types selected by the device in AuthenticationStart. Radiator implements them per RFC 8907:
authen_type | Round-trips | OTP follow-up via challenge, hotp, totp |
|---|---|---|
| ASCII | Multistage (server may prompt further) | Supported |
| PAP | Single request/reply | Not supported |
| CHAP | Single request/reply | Not supported |
| MSCHAP | Single request/reply | Not supported |
| MSCHAPv2 | Single request/reply | Not supported |
Only the ASCII authen_type may use the additional get-user, get-pass, and get-data continue rounds needed for interactive prompts and OTP follow-ups. The PAP, CHAP, MSCHAP, and MSCHAPv2 authen_type values must complete in a single exchange.
This restriction is on the TACACS+ authen_type field itself, not on what Radiator can validate inside an ASCII flow. Inside an ASCII multistage handler you can still:
- Compare a captured password against the backend with the
papaction (basic plaintext password comparison; the multistage example below uses it). - Validate a one-time password with
totp,hotpor other methods. - Combine the above in any order.
If you need a one-time password step, configure the device to start ASCII authentication for the relevant logins.
Minimal Listener (TCP)
clients {
tacacs-plus "TACACS_DEVICES" {
client "core-switch" {
source {
ip 10.10.1.10;
}
secret "SuperSecretKey!";
}
}
}
servers {
tacacs-plus "TACACS" {
listen {
protocol tcp;
port 49;
ip 0.0.0.0;
}
clients "TACACS_DEVICES";
session-timeout 15m;
policy "POLICY_TACACS_PLUS";
}
}
This example shows the built-in TACACS+ authorization session cache explicitly. 15m is also the default.
See clients.tacacs-plus for the full client reference and servers.tls for the TLS listener variant.
Multistage ASCII Authentication With Username, Password, and TOTP
This is the canonical multi-step interactive login flow. The device starts an ASCII authentication request. Radiator drives a sequence of get-user, get-pass, and get-data prompts using challenge, then validates the captured password with pap and the captured TOTP code with totp.
aaa {
policy "POLICY_TACACS_PLUS" {
handler "MULTISTAGE_TOTP" {
conditions all {
tacacsplus.authentication == true;
}
@execute {
# Prompt for the username if the client did not include one.
if any {
tacacsplus.request.user == none;
tacacsplus.request.user == "";
} then {
modify {
tacacsplus.reply.status = "get-user";
tacacsplus.reply.noecho = false;
}
challenge "Username";
if any {
tacacsplus.request.user == none;
tacacsplus.request.user == "";
} then {
modify tacacsplus.reply.message = "Username is required";
reject;
}
}
# Prompt for the password (no echo).
if all {
tacacsplus.request.password == none;
} then {
modify {
tacacsplus.reply.status = "get-pass";
tacacsplus.reply.noecho = true;
}
challenge "Password";
}
# Snapshot the password before the next prompt overwrites the
# shared TACACS+ message field. `pap response;` parks the
# captured response so a later `pap;` can validate it.
pap response;
# Prompt for the TOTP code (echo on).
modify {
tacacsplus.reply.status = "get-data";
tacacsplus.reply.noecho = false;
}
challenge "TOTP";
# Load the user record (password hash + TOTP secret).
backend {
name "TACACS_USERS";
query "FIND_USER";
}
# Validate the password captured in the get-pass round.
pap;
# Replace the parked password with the freshly captured TOTP
# response, then validate it as a TOTP code.
pap {
attribute tacacsplus.request.message;
response;
}
totp;
modify tacacsplus.reply.message = "Welcome";
}
}
}
}
How the rounds map onto TACACS+ packets:
- Device sends
AuthenticationStartwithauthen_type = ASCII. - Server replies with
status = get-user, prompt"Username". Device sendsAuthenticationContinuecarrying the username. - Server replies with
status = get-pass, prompt"Password". Device sendsAuthenticationContinuecarrying the password. - Server replies with
status = get-data, prompt"TOTP". Device sendsAuthenticationContinuecarrying the OTP. - Server validates both factors and replies with
status = passorstatus = fail.
Things to keep in mind:
- The
challengeaction returns control to the policy after the nextAuthenticationContinuearrives. Conditions evaluated after achallengecall see the new request fields. pap response;(block-less form) does not authenticate. It only stages the current message field as the captured PAP response so a laterpap;(or anotherpap response;) can use it. This pattern is required becausetacacsplus.request.messageis reused across rounds.- The same handler must validate every continue. Do not split a multistage flow across multiple handlers.
- Per RFC 8907, only ASCII supports
AuthenticationContinue. Radiator rejects continue rounds for PAP, CHAP, MSCHAP, and MSCHAPv2.
The example above shows the full multistage TACACS+ flow for username, password, and TOTP in one handler.
Authorization and Accounting
A single TACACS+ client typically also performs authorization and accounting. Add separate handlers gated on tacacsplus.authorization and tacacsplus.accounting:
The authorization example below expects a restored built-in TACACS+ session and rejects the request if there is no matching session. Accounting uses values from the accounting request itself.
handler "SHELL_AUTHORIZATION" {
conditions all {
tacacsplus.authorization == true;
}
@execute {
modify {
vars.args = tacacsplus.request.args;
}
if all {
user.username == none;
} then {
reject "no session";
}
if all {
user.group == "network-admins";
vars.args[0] == "service=shell";
vars.args[1] == "cmd=ls";
} then {
modify {
tacacsplus.reply.args += "priv-lvl=1";
tacacsplus.reply.args += "service=shell";
}
accept;
} else {
reject "command not allowed";
}
}
}
handler "TACACS_ACCOUNTING" {
conditions all {
tacacsplus.accounting == true;
}
@execute {
log "ACCOUNTING" {
json {
"user" tacacsplus.request.user;
"port" tacacsplus.request.port;
"address" tacacsplus.request.address;
"args" tacacsplus.request.args;
}
}
accept;
}
}
Accounting handlers should use the values carried by the accounting request itself, such as tacacsplus.request.user, tacacsplus.request.port, tacacsplus.request.address, and tacacsplus.request.args. Radiator does not restore cached user.* fields into accounting requests.
Built-in Authorization Session Cache
After a successful TACACS+ authentication, Radiator stores a sanitized snapshot of the user record so that subsequent authorization requests do not need to re-resolve the backend. The snapshot contains:
user.usernameuser.groupuser.roleuser.backenduser.privilege
Authentication-only state (password authenticator, per-user conditions, post-authentication callbacks) is not kept. To carry additional values across authentication and authorization, use a named caches block.
Entries are stored per TACACS+ server, keyed by server name, device IP address, TACACS+ client name, and username. Each successful authentication seeds an entry; each authorization cache hit refreshes its timeout. Lifetime is controlled by session-timeout in the servers { tacacs-plus "NAME" { ... } } block (durations like 30s, 15m, 1h; default 15m). Match it to the device's session timeout for the best end-user experience, or shorten it if you need tighter synchronization with the device's session lifecycle.
The cache is not aware of the device-side session, so an entry can expire while the device session is still active, or persist until timeout after the device session has ended. A few stale entries are inexpensive.
When no entry matches, Radiator still runs the authorization handler with user.* unset. Check user.username == none and either reject the request or re-resolve the user from the backend; re-resolving applies only to that one request and does not seed the cache (only authentication does).
Operational Notes
- Idle timeout. Per-client
timeoutoverrides the listenertimeout. The default is10swhen neither is set. See clients.tacacs-plus for the precedence rules. - Keepalive. Enable
keepaliveon the listener if you expect long-lived control sessions; it complements the idle timeout. ip-accept. Use the listenerip-acceptmap to drop connections from unexpected source ranges before any TACACS+ processing happens.- TLS client identity. When
require_client_certificate true;is set, Radiator first matches the client by source IP and then re-resolves it by certificate Subject Alternative Name. The TLS-side client must haveprotocol tls;in itssourceblock. - Diagnostics.
Rejecting non-TLS TACACS+ connection. Expecting TLS ClientHello byte 0x16indicates a plaintext connection arriving on a TLS listener.Rejecting TACACS+ TLS client connection: protocol not allowedmeans the resolved client does not haveprotocol tls;set.
See Also
- clients.tacacs-plus - Full client reference
- servers.tls - TACACS+ over TLS listener
- actions/challenge - Driving multistage prompts
- actions/pap - PAP validation and the
response;capture pattern - actions/totp - TOTP validation
- actions/hotp - HOTP validation
- TOTP/HOTP Authentication - OTP secret formats and storage
- PROXY Protocol Support - PROXY protocol limits on TLS listeners
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Comparison Operators
Configuration Editor
Configuration Import and Export
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Pipeline Directives
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables
About Radiator software development security
Architecture Overview
Backend Load Balancing
Basic Installation
Built-in Environment Variables
Comparison Operators
Configuration Editor
Configuration Import and Export
Data Types
Duration Units
Environment Variables
Execution Context
Execution Pipelines
Filters
Getting a Radiator License
Health check /live and /ready
High Availability and Load Balancing
High availability identifiers
HTTP Basic Authentication
Introduction
Linux systemd support
Local AAA Backends
Log storage and formatting
Management API privilege levels
Namespaces
Password Hashing
Pipeline Directives
Probabilistic Sampling
Prometheus scraping
PROXY Protocol Support
Radiator server health and boot up logic
Radiator sizing
Radiator software releases
Rate Limiting
Rate Limiting Algorithms
Reverse Dynamic Authorization
Service Level Objective
TACACS+ Authentication, Authorization, and Accounting
Template Rendering CLI
Tools radiator-client
TOTP/HOTP Authentication
What is Radiator?
YubiKey Authentication
YubiKey Context Variables