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) messages
  • AuthScopeNewWorkConns: 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 settings
  • VerifyPing (lines 71-80): Validates only if AuthScopeHeartBeats is present in the configuration
  • VerifyNewWorkConn (lines 82-90): Validates only if AuthScopeNewWorkConns is 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.GetAuthKey in pkg/util/util.go (lines 50-56), which hashes the shared token with a timestamp
  • Configure tokens statically or dynamically via tokenSource in pkg/config/v1/server.go and pkg/config/v1/client.go, with validators ensuring mutual exclusivity
  • Enable scope validation through additionalScopes to require cryptographic signing for heartbeats (AuthScopeHeartBeats) and new connections (AuthScopeNewWorkConns) defined in pkg/config/v1/common.go
  • The TokenAuthSetterVerifier in pkg/auth/token.go handles both signing (SetLogin, SetPing, SetNewWorkConn) and verification (VerifyLogin, VerifyPing, VerifyNewWorkConn), with login verification always enforced
  • Token sources resolve at startup via BuildServerAuth and BuildClientAuth in pkg/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →