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:
- First check the
X-Forwarded-Forheader, take the first IP address - If not available, check the
X-Real-IPheader - 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
}
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.
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:
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):
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
}
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:
// 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 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 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
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
- Understand Your Architecture: Clearly know how many proxy layers exist and which headers they use
- Don't Blindly Trust Headers: If there's no proxy, don't use headers like
X-Forwarded-For - Extract from Right: If using XFF, extract from right to left, skipping known proxy IPs
- Test: Test IP extraction logic before production
- Logging: Log extracted IPs and original headers for debugging
Related API
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.