Skip to content

GeoIP & Impossible Travel ​

GrydAuth uses GeoIP resolution to power impossible travel detection — a Zero Trust control that flags (and, by default, blocks or challenges) logins whose geographic displacement is physically implausible for the elapsed time. This page describes everything a consuming application needs to configure the control correctly and securely.

This control is fail-secure by default. Once GeoIP services are registered, an unavailable database or a critical geographic risk results in a step-up challenge or a block — never a silent allow. Read Security behavior before deploying, and see the Security Features page for the wider Zero Trust context.

How it works ​

Client IP ──▶ GeoIP lookup ──▶ location + accuracy radius
                                     │
                                     ▼
              per-device location history + trusted locations
                                     │
                                     ▼
              impossible-travel / hijack analysis (effective distance, velocity)
                                     │
                                     ▼
              GeoRiskSignal ──▶ IRiskDecisionPolicy ──▶ Allow | Step-up | Block
  1. The client IP is resolved to a city-level location (latitude/longitude plus an accuracy radius representing GeoIP uncertainty). Private/loopback/link-local and IPv6 ULA addresses are treated as local network and never trigger the control.
  2. The current location is compared against the user's recorded history for the same device and against the user's trusted locations.
  3. The service computes the effective distance and the velocity required to cover it, and derives a GeoRiskSignal (impossible travel, concurrent-session hijack, trusted, uncertainty, or database unavailability).
  4. A single decision policy (IRiskDecisionPolicy) maps the signal to one action: Allow (auditable), RequireStepUp, or Block.

Prerequisites ​

The default provider is MaxMind GeoLite2-City, which uses a local .mmdb database.

  1. Create a MaxMind account and generate a license key (the free GeoLite2 tier is sufficient): https://www.maxmind.com/en/geolite2/signup.
  2. Provide the license key to the application under the configuration key GrydAuth:GeoIp:MaxMindLicenseKey, keeping it out of source control. Use whatever configuration source your host already trusts — for local development, .NET user secrets (dotnet user-secrets set "GrydAuth:GeoIp:MaxMindLicenseKey" "<key>"); in other environments, the standard environment-variable form GrydAuth__GeoIp__MaxMindLicenseKey (double underscore) or your secret store. The framework binds this option through normal configuration.
  3. Provision the database in one of two ways:
    • Automatic (recommended): set EnableAutomaticUpdates: true. A background service downloads GeoLite2-City on startup and refreshes it on the configured interval, with SHA256 integrity verification and hot-reload (no restart required).
    • Manual: download GeoLite2-City.mmdb yourself and point DatabasePath at it. Automatic updates can then be disabled.

License key required for automatic updates

When EnableAutomaticUpdates is true for the MaxMind provider, MaxMindLicenseKey is mandatory and the application will fail fast at startup if it is missing.

Dependency injection ​

Register the GeoIP services in the consuming host. This binds and validates both option sections, wires the MaxMind reader (with hot-reload and integrity verification), and — when enabled — the health check and the background updater:

csharp
// Program.cs / host composition
builder.Services.AddGrydAuthGeoIpServices(builder.Configuration);

The risk decision policy (IRiskDecisionPolicy) and the Zero Trust security service that consumes these signals are registered by the Application layer as part of the standard GrydAuth registration — no extra call is needed for them.

Configuration ​

All keys live under the consolidated GrydAuth configuration section. A minimal appsettings.json for the MaxMind provider:

json
{
  "GrydAuth": {
    "GeoIp": {
      "Provider": "MaxMind",
      "DatabasePath": "./Data/GeoIP/GeoLite2-City.mmdb",
      "MaxMindAccountId": "",
      "MaxMindLicenseKey": "",
      "EnableAutomaticUpdates": true,
      "RequireChecksumVerification": true,
      "UpdateIntervalDays": 30,
      "EnableHealthCheck": true
    },
    "ImpossibleTravel": {
      "MaxVelocityKmh": 900.0,
      "TimeWindowMinutes": 60,
      "MinimumDistanceKm": 10.0,
      "EnforcementMode": "Enforce",
      "AutoBlockCriticalRisk": true,
      "MissingDatabaseBehavior": "FailClosed",
      "ConsiderAccuracyRadius": true,
      "MaxAccuracyRadiusKm": 200.0,
      "EnablePerDeviceHistory": true,
      "EvaluateConcurrentSessions": true,
      "ConcurrentSessionWindowMinutes": 60,
      "TrustedLocationRadiusKm": 50.0,
      "AutoLearnTrustedLocations": true,
      "MaxTrustedLocationsPerUser": 10,
      "MaxDeviceLocationsPerUser": 20,
      "NotifySecurityTeam": true
    }
  }
}

Removed legacy key

The database path key is GrydAuth:GeoIp:DatabasePath. The old GrydAuth:GeoIp:MaxMindDatabasePath key is no longer supported — it previously bound to nothing and was silently ignored. If present, the application now fails fast at startup with a message pointing to the correct key. MaxMindAccountId is a string ("" when unused), not a number.

GrydAuth:GeoIp ​

Provider, database lifecycle, and supply-chain integrity.

KeyTypeDefaultDescription
ProviderstringMaxMindProvider: MaxMind (local .mmdb), Azure, AWS, Google, or Custom.
DatabasePathstring./Data/GeoIP/GeoLite2-City.mmdbPath to the .mmdb database (MaxMind). Required.
MaxMindLicenseKeystring?nullMaxMind license key. Required when EnableAutomaticUpdates is true. Supply via user secrets or GrydAuth__GeoIp__MaxMindLicenseKey; keep out of source control.
MaxMindAccountIdstring?nullMaxMind account ID. Only needed for paid GeoIP2 databases.
EnableAutomaticUpdatesbooltrueDownload/refresh the database via the background updater.
UpdateIntervalDaysint (0–365)30Days between automatic updates (0 disables). GeoLite2 updates roughly monthly.
UpdateHourUtcint (0–23)3UTC hour to run scheduled updates.
RequireChecksumVerificationbooltrueVerify the SHA256 checksum of a downloaded database before installing (supply-chain protection).
ChecksumUrlTemplatestringMaxMind .sha256 URLTemplate ({0} = license key) for fetching the official checksum.
DownloadUrlTemplatestringMaxMind tar.gz URLTemplate ({0} = license key) for the database archive.
DownloadTimeoutSecondsint (30–3600)300Timeout for a download operation.
RetryCountint (0–10)3Retry attempts (exponential backoff) for failed downloads.
KeepDatabaseBackupbooltrueKeep the previous database version for rollback.
BackupCountint (0–10)2Number of backup versions retained.
EnableCachingbooltrueCache GeoIP lookup results.
CacheExpirationMinutesint (1–1440)60Lookup cache TTL.
EnableHealthCheckbooltrueRegister the grydauth-geoip health check and availability gauge.
EnableDetailedLoggingboolfalseVerbose GeoIP logging for troubleshooting.

Cloud providers use their own keys (AzureMapsSubscriptionKey, AwsRegion / AwsAccessKeyId / AwsSecretAccessKey, GoogleApiKey, CustomProviderUrl); invalid combinations fail fast at startup.

GrydAuth:ImpossibleTravel ​

Detection thresholds, enforcement, uncertainty handling, and per-device history.

KeyTypeDefaultDescription
MaxVelocityKmhdouble900.0Maximum realistic velocity (km/h). Above this, travel is impossible.
TimeWindowMinutesint60Time window over which consecutive logins are compared.
MinimumDistanceKmdouble10.0Distances below this are treated as the same location.
EnforcementModeenumEnforceEnforce acts on risk (block/step-up); Monitor audits only (opt-in downgrade).
AutoBlockCriticalRiskbooltrueUnder Enforce, block critical/impossible travel. When false, require step-up instead.
MissingDatabaseBehaviorenumFailClosedFailClosed requires step-up when the database is unavailable; FailOpen skips validation with an auditable warning.
ConsiderAccuracyRadiusbooltrueSubtract the (capped) GeoIP accuracy radii from the central distance to reduce false positives. Set false for the raw central distance.
MaxAccuracyRadiusKmdouble200.0Per-reading cap on the accuracy radius, so imprecise readings cannot neutralize detection.
EnablePerDeviceHistorybooltrueCompare each login against the last location of the same device.
EvaluateConcurrentSessionsbooltrueDetect a geographically incompatible active session on another device (possible hijack).
ConcurrentSessionWindowMinutesint60Window within which another device's location counts as concurrent for the hijack check.
TrustedLocationRadiusKmdouble50.0Radius around a trusted location within which a login is treated as trusted.
AutoLearnTrustedLocationsbooltruePromote a plausible, allowed login location to the user's trusted locations.
MaxTrustedLocationsPerUserint10Retention cap on trusted locations per user (privacy/LGPD).
MaxDeviceLocationsPerUserint20Retention cap on per-device location entries per user (privacy/LGPD).
NotifySecurityTeambooltrueNotify the security team on high/critical risk.
SecurityTeamEmailstring?nullRecipient for security notifications.

Security behavior ​

  • Fail-secure enforcement. With EnforcementMode: Enforce (default), critical / impossible travel is blocked when AutoBlockCriticalRisk is true, otherwise it requires step-up. High/medium risk requires step-up. Monitor mode downgrades every action to an audited allow — an explicit, opt-in choice.
  • Fail-closed on missing database. With MissingDatabaseBehavior: FailClosed (default), an unavailable database routes the request to step-up rather than skipping geographic validation. Set FailOpen to skip it with an auditable warning.
  • Fail-fast configuration. Invalid configuration (for example, automatic updates without a license key, or the removed legacy key) aborts startup with a clear message.
  • Supply-chain integrity. Downloaded databases are verified against the official SHA256 checksum before install (RequireChecksumVerification, default true). On a mismatch or an unavailable checksum, the install is aborted and the current database is kept; the updater retries on the next window. Disabling this weakens supply-chain protection and is not recommended.
  • Hot-reload. A successful update swaps the active database atomically, draining in-flight lookups first — updates take effect without a restart, and a corrupt new file leaves the previous database serving traffic.
  • Effective distance (uncertainty). When ConsiderAccuracyRadius is true, the decision uses EffectiveDistanceKm = max(0, centralDistance − cappedRadiusPrev − cappedRadiusCurr). This cuts false positives from imprecise readings (CGNAT, VPN, mobile IP) without masking genuinely continental jumps. The response still reports the raw central distance in DistanceKm; read EffectiveDistanceKm for the value the decision was based on.
  • Per-device history & trusted locations. Detection is scoped per device (send the device fingerprint on the Zero Trust request for maximum benefit; without one, a sentinel bucket is used). Alternating between legitimate devices no longer produces false impossible travel, while the same device making an impossible jump still blocks. Trusted locations reduce plausible-but-suspicious high/medium risk to an audited allow — they never rescue real impossible travel or a hijack signal.
  • Concurrent-session hijack. If another of the user's devices is active in a geographically incompatible location within the concurrency window, the login is escalated as impossible travel (block/step-up per the enforcement settings).

Observability ​

  • Health check grydauth-geoip (tags geoip, grydauth) reports Unhealthy when the database is unavailable. Gated by GeoIp:EnableHealthCheck.
  • Metric geoip_database_available — gauge, 1 when the database is loaded, 0 when missing.
  • Tracing tags: zero_trust.geo_decision (allow | step_up | block), and DistanceKm / EffectiveDistanceKm on the analysis activity.
  • Reason codes on emitted security events: impossible_travel, concurrent_session_hijack, trusted_location, geoip_database_unavailable, geo_risk_calculation_failed.

Troubleshooting ​

SymptomLikely causeResolution
Startup fails with an options-validation errorEnableAutomaticUpdates: true without MaxMindLicenseKey, or the removed MaxMindDatabasePath key is setProvide MaxMindLicenseKey (user secrets or GrydAuth__GeoIp__MaxMindLicenseKey) or disable automatic updates; rename the key to DatabasePath.
Every login requires step-up; health check is UnhealthyDatabase missing/unavailable and MissingDatabaseBehavior: FailClosedProvision GeoLite2-City.mmdb at DatabasePath (or enable automatic updates). FailOpen is available as an explicit opt-out.
Database never updatesAutomatic updates disabled, or the checksum could not be fetched/verifiedSet EnableAutomaticUpdates: true with a valid license key; check outbound access to MaxMind and the security logs — a failed checksum keeps the current database and retries.
Legitimate users blocked as impossible travel (VPN/NAT/mobile)Imprecise GeoIP readingsKeep ConsiderAccuracyRadius: true; tune MaxAccuracyRadiusKm and TrustedLocationRadiusKm. Ensure the device fingerprint is sent so per-device history applies.
Switching devices flags impossible travelPer-device history disabled or no device fingerprintKeep EnablePerDeviceHistory: true and send a stable device fingerprint on the Zero Trust request.
Update applied but behavior unchangedExpecting a restartNot required — the reader hot-reloads after a verified update. Confirm the update succeeded in the logs.

Released under the MIT License.