VLESS UUID Authentication: How It Works and How It Differs From VMess
VLESS authenticates clients using a plain-text UUID without any cryptographic handshake, while VMess combines UUID with security types in a full AEAD key exchange.
The VLESS protocol in XTLS/Xray-core takes a deliberately minimalist approach to authentication. Unlike its predecessor VMess, VLESS separates identity verification from encryption entirely. This article examines how VLESS UUID authentication works under the hood, referencing the actual source implementation, and contrasts it with VMess's more complex security model.
How VLESS UUID Authentication Works
The Core Architecture
VLESS authentication centers on the MemoryValidator struct in proxy/vless/validator.go. When a VLESS inbound starts, each configured client becomes a protocol.MemoryUser whose Account field holds a UUID string. The validator stores these users in two concurrent maps:
users— keyed by a processed UUIDemail— optional reverse-lookup by email address
// proxy/vless/validator.go
type MemoryValidator struct {
users sync.Map // map[[16]byte]*protocol.MemoryUser
email sync.Map // map[string]*protocol.MemoryUser
}
UUID Processing: Removing VMess Legacy
Before storage, VLESS processes every UUID through ProcessUUID:
// proxy/vless/validator.go
func ProcessUUID(id [16]byte) [16]byte {
id[6] = 0
id[7] = 0
return id
}
This function zeroes bytes 6 and 7 — the "version" and "variant" fields in standard UUID format. VMess historically used these bits to encode security type information. VLESS strips them to extract only the identity portion, making UUIDs purely identifiers without embedded security metadata.
Authentication Flow
When a client connects, the VLESS inbound handler in proxy/vless/inbound/inbound.go performs these steps:
// Simplified excerpt from inbound.go handleRequest
func (h *Handler) handleRequest(ctx context.Context, conn net.Conn) error {
// 1. Read exactly 16 bytes for client UUID
var rawID [16]byte
if _, err := io.ReadFull(conn, rawID[:]); err != nil {
return err
}
// 2. Parse as UUID type
clientID, err := uuid.ParseBytes(rawID[:])
if err != nil {
return err
}
// 3. Authenticate via validator
user := h.validator.Get(clientID)
if user == nil {
return errors.New("failed to get VLESS user")
}
// 4. Access account metadata for flow/encryption decisions
account := user.Account.(*vless.MemoryAccount)
// ... continue with flow logic based on account.Flow, etc.
}
The Get method in MemoryValidator looks up the processed UUID:
// proxy/vless/validator.go
func (v *MemoryValidator) Get(id uuid.UUID) *protocol.MemoryUser {
u, _ := v.users.Load(ProcessUUID(id))
if u != nil {
return u.(*protocol.MemoryUser)
}
return nil
}
If no user matches, the connection aborts immediately with failed to get VLESS user. No retry, no fallback—authentication is strictly binary.
VLESS vs VMess: Key Differences
| Aspect | VLESS | VMess |
|---|---|---|
| Authentication token | Plain UUID (identity only) | UUID plus security type (aes-128-gcm, chacha20-poly1305, etc.) |
| Handshake overhead | None—server checks UUID and proceeds | Full KDF-based key exchange with AEAD encryption |
| Header encryption | Optional (decryption field for XOR/REALITY); not required for identity |
Always encrypted via SealVMessAEADHeader in encoding/client.go |
| Payload security | Depends on optional flow (none, XTLS, REALITY); can be plaintext | Enforced by chosen security; invalid without it |
| UUID storage | ProcessUUID strips version bits; pure identity lookup |
Raw UUID includes security encoding; lookup incorporates security type |
| Reverse proxy support | Reverse field in MemoryAccount; UUID-only gating |
Similar capability but tied to security-specific UUID |
VMess Authentication Deep-Dive
To understand why VLESS differs, examine VMess's approach in proxy/vmess/account.go and proxy/vmess/vmess.go:
// proxy/vmess/account.go — Account couples UUID with security
type Account struct {
Id string // UUID string
AlterId uint32 // Legacy parameter
Security string // "aes-128-gcm", "chacha20-poly1305", "auto", etc.
}
The VMess handshake in common/protocol/headers.go and proxy/vmess/encoding/client.go derives session keys from both the UUID and security type:
// Simplified from encoding/client.go
kdf := hmac.New(sha256.New, []byte("VMessBF"))
kdf.Write(id.Bytes())
kdf.Write(securityTypeBytes())
// ... derive keys for AEAD encryption
This KDF-based derivation means VMess cannot authenticate without also negotiating encryption. The UUID and security type are inseparable. VLESS's ProcessUUID explicitly removes this coupling, allowing the same UUID to work with any encryption flow (or none at all).
Practical Configuration Examples
Minimal VLESS Server Configuration
{
"inbounds": [
{
"protocol": "vless",
"port": 443,
"settings": {
"clients": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"flow": "xtls-rprx-vision",
"encryption": "none"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "tls",
"tlsSettings": {
"certificates": []
}
}
}
]
}
Key points:
idis the UUID passed verbatim toMemoryValidatorencryption:"none"disables VLESS-level payload encryption (TLS/XTLS handles transport security)decryptionin settings mirrors this for the server side
VMess Equivalent for Comparison
{
"inbounds": [
{
"protocol": "vmess",
"port": 443,
"settings": {
"clients": [
{
"id": "11223344-5566-7788-99aa-bbccddeeff00",
"alterId": 64,
"security": "aes-128-gcm"
}
]
}
}
]
}
Here security is mandatory and tightly coupled to the UUID—no separate encryption layer selection exists.
Summary
-
VLESS uses a plain UUID for authentication, stripping version bits via
ProcessUUIDinproxy/vless/validator.goto create a pure identity lookup key. -
No cryptographic handshake occurs—the server reads 16 bytes, processes the UUID, and checks
MemoryValidator.usersvia a concurrent map lookup. -
Encryption is completely decoupled from authentication, enabled optionally through
flow(XTLS) orstreamSettings(TLS/REALITY) rather than being embedded in the UUID. -
VMess intertwines UUID and security, requiring a KDF-based handshake and AEAD header encryption that binds identity to encryption method.
-
Performance impact: VLESS's lightweight authentication reduces connection establishment overhead, particularly beneficial for high-concurrency scenarios and TLS-fingerprinting resistance via REALITY.
Frequently Asked Questions
Can VLESS work without any encryption?
Yes, though this is not recommended for production. Setting encryption:"none" and security:"none" transmits payloads in plaintext. The UUID authentication still occurs, but traffic is readable to any observer. Most deployments use TLS or XTLS for transport security instead.
Why does VLESS process the UUID by zeroing bytes 6-7?
These bytes historically encoded VMess security type information. VLESS removes this coupling to treat UUIDs as pure identifiers. The ProcessUUID function in proxy/vless/validator.go ensures compatibility with standard UUID formats while eliminating legacy VMess metadata dependencies.
Can the same UUID work with both VLESS and VMess?
Not simultaneously in a meaningful way. While both protocols accept standard UUID formats, VMess interprets the UUID as part of its security handshake, while VLESS processes it through ProcessUUID. A server configured for both protocols would need separate inbound configurations with distinct UUIDs or careful parameter mapping to avoid conflicts.
What happens if a VLESS client sends an invalid UUID?
The connection aborts immediately. In proxy/vless/inbound/inbound.go, the handler calls validator.Get(clientID) and returns errors.New("failed to get VLESS user") if the lookup returns nil. No retry mechanism exists—invalid UUIDs result in silent connection termination without revealing server configuration details.
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 →