# How to Secure frp with Token Authentication and Scope Validation

> Secure frp with token authentication and scope validation. Learn how frp uses hashed tokens and timestamps for privilege key generation enhancing login heartbeat and connection security.

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/client.go) (lines 80-96). The validators in [`pkg/config/v1/validation/server.go`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/validation/server.go) and [`pkg/config/v1/validation/client.go`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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)

```ini
[common]
bindPort = 7000

[auth]
token = mySecretToken123
additionalScopes = HeartBeats,NewWorkConns

```

### Client with Static Token (frpc.ini)

```ini
[common]
serverAddr = x.x.x.x
serverPort = 7000

[auth]
token = mySecretToken123
additionalScopes = HeartBeats,NewWorkConns

```

### Client with File-Based Token

```ini
[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

```ini
[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:

```go
// 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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/server.go) and [`pkg/config/v1/client.go`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/pkg/config/v1/common.go)
- The `TokenAuthSetterVerifier` in [`pkg/auth/token.go`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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`](https://github.com/fatedier/frp/blob/main/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.