How to Secure frp with Token Authentication and Scope Validation
frp uses a shared secret token hashed with timestamps to generate privilege keys that authenticate login, heartbeat, and new work connection messages, with optional scope validation restricting which control plane operations require cryptographic verification.
The fatedier/frp project provides a fast reverse proxy to expose local services behind NATs and firewalls. To prevent unauthorized tunnel creation, the framework implements a lightweight token authentication mechanism combined with granular scope validation that lets you control exactly which messages must carry cryptographic proof.
Understanding the Privilege Key Mechanism
At the core of frp's security model is the privilege key, a time-bound cryptographic hash generated from the shared token. According to the source code in pkg/util/util.go (lines 50-56), the GetAuthKey function computes an MD5 hash of the token concatenated with the current Unix timestamp to produce this key.
When a client initiates a connection, the TokenAuthSetterVerifier interface in pkg/auth/token.go handles message signing. The SetLogin method (lines 39-42) embeds the privilege key into msg.Login.PrivilegeKey, while optional scopes trigger SetPing (lines 44-51) for heartbeats and SetNewWorkConn (lines 54-61) for new tunnel requests.
Configuring Token Sources on Server and Client
Both the server (frps) and client (frpc) expose an auth block supporting two mutually exclusive fields defined in their respective configuration structures. In pkg/config/v1/server.go (lines 28-33), the AuthServerConfig struct contains Token and TokenSource fields. The client-side mirror, AuthClientConfig, lives in pkg/config/v1/client.go (lines 80-96). The validators in pkg/config/v1/validation/server.go and pkg/config/v1/validation/client.go enforce that you cannot define both fields simultaneously (lines 39-44 and 68-73).
Static Token Configuration
The simplest approach uses a hardcoded string. Both configurations accept a token field under the [auth] section that serves as the pre-shared secret.
Dynamic Token Sources
For enhanced security, frp supports dynamic token retrieval through the tokenSource field. As implemented in pkg/auth/auth.go (lines 4-11 and 45-54), the BuildServerAuth and BuildClientAuth functions resolve these sources at startup. Supported types include:
- file: Reads the token from a specified file path on disk
- exec: Executes an external command to retrieve the token (requires the unsafe feature flag)
Implementing Scope Validation
Scope validation allows you to restrict privilege key verification to specific message types, reducing computational overhead while maintaining security for critical operations. The supported scopes are defined as constants in pkg/config/v1/common.go (lines 35-40):
AuthScopeHeartBeats: Requires signed heartbeat (ping) messagesAuthScopeNewWorkConns: Requires signed new work connection requests
You enable these by populating the additionalScopes array in your configuration.
Server-Side Verification Logic
The server-side verifier in pkg/auth/token.go implements three distinct verification methods:
VerifyLogin(lines 64-68): Always validates the privilege key, regardless of scope settingsVerifyPing(lines 71-80): Validates only ifAuthScopeHeartBeatsis present in the configurationVerifyNewWorkConn(lines 82-90): Validates only ifAuthScopeNewWorkConnsis enabled
Client-Side Message Signing
Correspondingly, the client only generates privilege keys for enabled scopes. If you omit AuthScopeHeartBeats from the additionalScopes array, the client skips cryptographic signing for ping messages, and the server's VerifyPing function permits the message without checking the key.
Complete Configuration Examples
Server Configuration (frps.ini)
[common]
bindPort = 7000
[auth]
token = mySecretToken123
additionalScopes = HeartBeats,NewWorkConns
Client with Static Token (frpc.ini)
[common]
serverAddr = x.x.x.x
serverPort = 7000
[auth]
token = mySecretToken123
additionalScopes = HeartBeats,NewWorkConns
Client with File-Based Token
[common]
serverAddr = x.x.x.x
serverPort = 7000
[auth]
tokenSource.type = file
tokenSource.file.path = /etc/frp/token.txt
additionalScopes = HeartBeats
Client with Exec-Based Token
[auth]
tokenSource.type = exec
tokenSource.exec.command = /usr/local/bin/get-frp-token
tokenSource.exec.args = ["--client"]
Note that the exec source requires explicitly enabling the unsafe feature flag due to the security implications of executing arbitrary commands.
Programmatic Authentication Runtime
Internally, frp constructs the authentication runtime using configuration builders. The following Go pattern demonstrates how the server initializes its verifier:
// Resolve config and build auth runtime (server side)
srvAuth, err := auth.BuildServerAuth(&cfg.Auth)
if err != nil {
// Handle configuration error (invalid token/tokenSource combination)
}
loginVerifier := srvAuth.Verifier // Implements VerifyLogin, VerifyPing, VerifyNewWorkConn
The BuildClientAuth function performs equivalent resolution for client configurations, returning a setter interface that populates the PrivilegeKey fields in protocol messages before transmission.
Summary
- frp authenticates clients using privilege keys generated by
util.GetAuthKeyinpkg/util/util.go(lines 50-56), which hashes the shared token with a timestamp - Configure tokens statically or dynamically via
tokenSourceinpkg/config/v1/server.goandpkg/config/v1/client.go, with validators ensuring mutual exclusivity - Enable scope validation through
additionalScopesto require cryptographic signing for heartbeats (AuthScopeHeartBeats) and new connections (AuthScopeNewWorkConns) defined inpkg/config/v1/common.go - The
TokenAuthSetterVerifierinpkg/auth/token.gohandles both signing (SetLogin,SetPing,SetNewWorkConn) and verification (VerifyLogin,VerifyPing,VerifyNewWorkConn), with login verification always enforced - Token sources resolve at startup via
BuildServerAuthandBuildClientAuthinpkg/auth/auth.go(lines 4-11 and 45-54)
Frequently Asked Questions
What happens if the client and server tokens do not match?
The server rejects the connection during the login phase. Specifically, VerifyLogin in pkg/auth/token.go (lines 64-68) computes the expected privilege key from the server's token and compares it against the key received from the client in msg.Login.PrivilegeKey. A mismatch results in immediate authentication failure before any tunnel is established.
Can I use token authentication without enabling additional scopes?
Yes. By default, frp only requires authentication during the initial login. If you omit additionalScopes or leave the array empty, the system validates the privilege key exclusively during VerifyLogin, skipping cryptographic verification for subsequent heartbeat and new work connection messages to reduce overhead.
How does the exec token source work, and why is it considered unsafe?
The exec source runs an external command specified in tokenSource.exec.command to retrieve the token dynamically at startup. Because executing arbitrary commands introduces risks of command injection, environment variable leakage, and side effects, frp requires explicitly enabling an unsafe feature flag to use tokenSource.type = exec. File-based sources do not carry this restriction.
Where does frp store the resolved token in memory?
The BuildServerAuth and BuildClientAuth functions in pkg/auth/auth.go resolve the token at startup and store it within the TokenAuthSetterVerifier struct instance. The actual string resides in memory as part of the runtime authentication object and is never written to disk after resolution, though it remains in the process heap for the duration of the session.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →