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 UUID
  • email — 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:

  • id is the UUID passed verbatim to MemoryValidator
  • encryption:"none" disables VLESS-level payload encryption (TLS/XTLS handles transport security)
  • decryption in 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 ProcessUUID in proxy/vless/validator.go to create a pure identity lookup key.

  • No cryptographic handshake occurs—the server reads 16 bytes, processes the UUID, and checks MemoryValidator.users via a concurrent map lookup.

  • Encryption is completely decoupled from authentication, enabled optionally through flow (XTLS) or streamSettings (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:

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 →