Packages
phoenix_kit
1.7.85
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/utils/session_fingerprint.ex
defmodule PhoenixKit.Utils.SessionFingerprint do
@moduledoc """
Session fingerprinting utilities for preventing session hijacking.
This module provides functions to create and verify session fingerprints based on
IP address and user agent data. These fingerprints help detect when a session token
is being used from a different location or device than where it was created.
## Security Considerations
- IP addresses can change (mobile users, VPNs, etc.), so strict enforcement may
impact legitimate users
- User agents can be spoofed, but provide an additional layer of verification
- This is defense-in-depth: fingerprinting complements, not replaces, other security measures
## Configuration
You can configure the strictness level in your application config:
config :phoenix_kit,
session_fingerprint_enabled: true,
session_fingerprint_strict: false # true = force re-auth, false = log warnings
## Examples
# Create a fingerprint from a connection
fingerprint = SessionFingerprint.create_fingerprint(conn)
# Verify a fingerprint
case SessionFingerprint.verify_fingerprint(conn, stored_ip, stored_ua_hash) do
:ok -> # Fingerprint matches
{:warning, :ip_mismatch} -> # IP changed, but might be legitimate
{:warning, :user_agent_mismatch} -> # User agent changed
{:error, :fingerprint_mismatch} -> # Both changed, likely hijacked
end
"""
require Logger
@hash_algorithm :sha256
@enforce_keys [:ip_address, :user_agent_hash]
defstruct [:ip_address, :user_agent_hash]
@type t :: %__MODULE__{
ip_address: String.t(),
user_agent_hash: String.t()
}
@doc """
Creates a session fingerprint from a Plug.Conn connection.
Returns a `%SessionFingerprint{}` struct with `:ip_address` and `:user_agent_hash` fields.
## Examples
iex> create_fingerprint(conn)
%SessionFingerprint{ip_address: "192.168.1.1", user_agent_hash: "a1b2c3d4..."}
"""
def create_fingerprint(conn) do
%__MODULE__{
ip_address: get_ip_address(conn),
user_agent_hash: hash_user_agent(conn)
}
end
@doc """
Extracts the IP address from a connection.
Handles proxied connections by checking X-Forwarded-For and X-Real-IP headers,
falling back to the direct connection IP.
## Examples
iex> get_ip_address(conn)
"192.168.1.1"
"""
def get_ip_address(conn) do
# Check for proxied IP addresses first
cond do
# X-Forwarded-For header (may contain multiple IPs, take the first)
forwarded_for = get_header(conn, "x-forwarded-for") ->
forwarded_for
|> String.split(",")
|> List.first()
|> String.trim()
# X-Real-IP header
real_ip = get_header(conn, "x-real-ip") ->
String.trim(real_ip)
# Direct connection IP
true ->
conn.remote_ip
|> :inet.ntoa()
|> to_string()
end
rescue
_ ->
# Fallback to "unknown" if IP extraction fails
"unknown"
end
@doc """
Extracts and hashes the user agent from a connection.
Returns a SHA256 hash of the user agent string for privacy and storage efficiency.
## Examples
iex> hash_user_agent(conn)
"a1b2c3d4e5f6..."
"""
def hash_user_agent(conn) do
user_agent = get_header(conn, "user-agent") || "unknown"
:crypto.hash(@hash_algorithm, user_agent)
|> Base.encode16(case: :lower)
end
@doc """
Verifies a session fingerprint against the current connection.
Returns:
- `:ok` if fingerprint matches
- `{:warning, :ip_mismatch}` if only IP changed
- `{:warning, :user_agent_mismatch}` if only user agent changed
- `{:error, :fingerprint_mismatch}` if both changed
- `:ok` if stored fingerprint is nil (backward compatibility)
## Examples
iex> verify_fingerprint(conn, "192.168.1.1", "abc123")
:ok
iex> verify_fingerprint(conn, "10.0.0.1", "abc123")
{:warning, :ip_mismatch}
"""
def verify_fingerprint(conn, stored_ip, stored_ua_hash) do
# Backward compatibility: if no fingerprint was stored, allow access
if is_nil(stored_ip) and is_nil(stored_ua_hash) do
:ok
else
current_ip = get_ip_address(conn)
current_ua_hash = hash_user_agent(conn)
ip_matches? = is_nil(stored_ip) or stored_ip == current_ip
ua_matches? = is_nil(stored_ua_hash) or stored_ua_hash == current_ua_hash
case {ip_matches?, ua_matches?} do
{true, true} ->
:ok
{false, true} ->
Logger.warning("""
PhoenixKit: Session IP mismatch detected
Stored IP: #{stored_ip}
Current IP: #{current_ip}
User Agent matches: yes
""")
{:warning, :ip_mismatch}
{true, false} ->
Logger.warning("""
PhoenixKit: Session User-Agent mismatch detected
IP matches: yes
User Agent changed
""")
{:warning, :user_agent_mismatch}
{false, false} ->
Logger.error("""
PhoenixKit: Session fingerprint mismatch - possible hijacking attempt
Stored IP: #{stored_ip}
Current IP: #{current_ip}
User Agent also changed
""")
{:error, :fingerprint_mismatch}
end
end
end
@doc """
Checks if session fingerprinting is enabled in the application config.
## Examples
iex> fingerprinting_enabled?()
true
"""
def fingerprinting_enabled? do
PhoenixKit.Config.get_boolean(:session_fingerprint_enabled, true)
end
@doc """
Checks if strict fingerprint verification is enabled.
When strict mode is enabled, fingerprint mismatches will force re-authentication.
When disabled, mismatches only log warnings.
## Examples
iex> strict_mode?()
false
"""
def strict_mode? do
PhoenixKit.Config.get_boolean(:session_fingerprint_strict, false)
end
# Private helper to get a header value from connection
defp get_header(conn, header_name) do
case Plug.Conn.get_req_header(conn, header_name) do
[value | _] -> value
[] -> nil
end
end
end