Skip to main content

IP Address

IP addresses play a fundamental role in HTTP; they are used for access control, auditing, geolocation-based access analysis, etc. Slim provides a convenient method Context#RealIP() to easily retrieve them.

However, retrieving the real IP address from a request is not straightforward, especially when we use certain proxy technologies. In this case, the real IP needs to be relayed from the proxy to our application at the HTTP layer, so we cannot unconditionally trust HTTP headers, otherwise we may get fake addresses, creating security risks!

To reliably and securely retrieve IP addresses, the application must be aware of the entire architecture of the infrastructure. In Slim, this can be done by properly configuring Slim#IPExtractor. This guide shows you why and how.

Default Behavior

If we don't explicitly set a value for the Slim instance property Slim#IPExtractor, Context#RealIP() uses the following fallback logic:

  1. First check the X-Forwarded-For header, take the first IP address
  2. If not available, check the X-Real-IP header
  3. Finally fall back to Request.RemoteAddr
func (c *contextImpl) RealIP() string {
if c.slim.IPExtractor != nil {
return c.slim.IPExtractor(c.request)
}
// Fall back to default behavior
if ip := c.request.Header.Get("X-Forwarded-For"); ip != "" {
// Take the first IP
i := strings.IndexAny(ip, ",")
if i > 0 {
return strings.TrimSpace(ip[:i])
}
return ip
}
if ip := c.request.Header.Get("X-Real-IP"); ip != "" {
return ip
}
// Extract IP from RemoteAddr
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
return ra
}
warning

The default behavior may not be secure! If clients can directly access your application (without a proxy), they can forge X-Forwarded-For or X-Real-IP headers.

Custom IP Extractor

IPExtractor is a function type used to extract IP addresses from http.Request:

type IPExtractor func(*http.Request) string

You can implement a custom IP extractor based on your own infrastructure.

Scenario 1: No Proxy (Direct Connection)

If you don't have a proxy in place (e.g., internet-facing service), you only need (and must) use the network layer's IP address. Any HTTP headers are untrustworthy because the client has complete control over which headers to set.

Extract IP directly from RemoteAddr
s := slim.New()

// Only trust RemoteAddr, ignore all headers
s.IPExtractor = func(r *http.Request) string {
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

Scenario 2: Using Trusted Proxy

If your application is behind one or more trusted proxies (like Nginx, cloud load balancers, etc.), you need to extract the IP based on headers set by the proxy.

Using X-Real-IP Header

Some proxies (like Nginx) use the X-Real-IP header to pass the client IP:

Extract from X-Real-IP
s.IPExtractor = func(r *http.Request) string {
if ip := r.Header.Get("X-Real-IP"); ip != "" {
return ip
}
// Fall back to RemoteAddr
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

Using X-Forwarded-For Header

X-Forwarded-For (XFF) is a commonly used header for relaying client IP addresses. At each hop of the proxy, they append the request IP address at the end of the header.

The following example diagram demonstrates this behavior:

            ┌──────────┐            ┌──────────┐            ┌──────────┐
───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
Client (a) │ (IP: b) │ │ (IP: c) │ │ │
└──────────┘ └──────────┘ └──────────┘

Case 1. Client directly connects to Proxy 1
XFF: "" "a" "a, b"
~~~~~~

Case 2. Client already through proxy x
XFF: "x" "x, a" "x, a, b"
~~~~~~~~~
↑ What your application will see

Extract from Left (Least Secure):

Extract first IP from XFF (not recommended)
s.IPExtractor = func(r *http.Request) string {
xff := r.Header.Get("X-Forwarded-For")
if xff == "" {
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}
// Take the first IP
i := strings.IndexAny(xff, ",")
if i > 0 {
return strings.TrimSpace(xff[:i])
}
return xff
}
danger

This method is not secure! Clients can forge the X-Forwarded-For header. Only use when you're certain all traffic goes through trusted proxies.

Extract from Right (Recommended):

If you know the number of proxies (e.g., 1 load balancer), you can extract from right to left:

Extract Nth IP from right in XFF
// Assuming 1 trusted proxy
s.IPExtractor = func(r *http.Request) string {
xff := r.Header.Get("X-Forwarded-For")
if xff == "" {
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

ips := strings.Split(xff, ",")
// Second from right (skip the last IP added by proxy)
if len(ips) >= 2 {
return strings.TrimSpace(ips[len(ips)-2])
}
// If only one IP, return it directly
return strings.TrimSpace(ips[0])
}

Scenario 3: Cloud Platforms (AWS, GCP, etc.)

Cloud platforms typically have their own headers:

AWS ALB/ELB
// AWS uses X-Forwarded-For
s.IPExtractor = func(r *http.Request) string {
// AWS ALB adds client IP at the leftmost position of XFF
xff := r.Header.Get("X-Forwarded-For")
if xff == "" {
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

ips := strings.Split(xff, ",")
// Take the first (client's real IP)
return strings.TrimSpace(ips[0])
}
Cloudflare
// Cloudflare uses CF-Connecting-IP
s.IPExtractor = func(r *http.Request) string {
if ip := r.Header.Get("CF-Connecting-IP"); ip != "" {
return ip
}
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

Complete Example

Production environment example
package main

import (
"net"
"net/http"
"strings"

"go-slim.dev/slim"
)

func main() {
s := slim.New()

// Configure IP extractor (choose based on your infrastructure)
s.IPExtractor = extractIPFromXFF

s.GET("/", func(c slim.Context) error {
ip := c.RealIP()
return c.String(http.StatusOK, "Your IP: " + ip)
})

s.Start(":1324")
}

// Extract IP from X-Forwarded-For (assuming 1 trusted proxy)
func extractIPFromXFF(r *http.Request) string {
xff := r.Header.Get("X-Forwarded-For")
if xff == "" {
ra, _, _ := net.SplitHostPort(r.RemoteAddr)
return ra
}

ips := strings.Split(xff, ",")
if len(ips) >= 2 {
// Skip the last one (proxy's IP)
return strings.TrimSpace(ips[len(ips)-2])
}
return strings.TrimSpace(ips[0])
}

Best Practices

  1. Understand Your Architecture: Clearly know how many proxy layers exist and which headers they use
  2. Don't Blindly Trust Headers: If there's no proxy, don't use headers like X-Forwarded-For
  3. Extract from Right: If using XFF, extract from right to left, skipping known proxy IPs
  4. Test: Test IP extraction logic before production
  5. Logging: Log extracted IPs and original headers for debugging

Context#RealIP()

func (c Context) RealIP() string

Returns the client's real IP address. Behavior is configured by Slim#IPExtractor.

Slim#IPExtractor

type IPExtractor func(*http.Request) string

type Slim struct {
// ...
IPExtractor IPExtractor
// ...
}

IP extractor function. If nil, uses default fallback behavior.