Why you might need an Apex user IP address
If you've ever debugged a strange security issue, or had to show a banner only to users in one region, you know how handy the Apex user IP address is. I've worked on projects where we had to log exactly where a request came from to satisfy some strict audit requirements. It seems simple until you go looking for the right class to call.
Salesforce also stores a decent amount of geographic data behind the scenes. Combine the IP with login history and you get the city, state, and even the postal code, no external service required. That is enough to make a Salesforce universal logging framework tell you something useful about your users.

A realistic Salesforce Lightning interface showing a log entry with user IP address and geographic location details.
How to fetch the Apex user IP address and location
The class you want is Auth.SessionManagement, which holds the session details. Grabbing the IP is the easy half. For the location you have to hop from the session to the login history, and from there to the geo-location table.
The data isn't always there, either. If the user is on a VPN, or Salesforce hasn't mapped that IP range yet, the geo-location fields come back empty. Plan for nulls. I've seen teams fill their logs with NullPointerExceptions because they assumed every IP has a city attached to it.
Step 1: Grabbing the session IP
Pull the IP out of the current session map with the SourceIp key. I wrap that in a Test.isRunningTest() check, because unit tests have no active session and the code breaks the moment one runs without a guard.
Step 2: Finding the Geo-Location data
Once you have the LoginHistoryId from the session, query the LoginHistory object. It holds a lookup to LoginGeo, and that is where the location data lives: the subdivision (usually the state or province), the city, and the country. It's surprisingly accurate for most office-based users.
The Apex implementation
public void captureUserSessionData(List
String ipAddress = !Test.isRunningTest() ? sessionAttributes.get('SourceIp') : '1.1.1.1';
String loginId = !Test.isRunningTest() ? sessionAttributes.get('LoginHistoryId') : null;
if (String.isBlank(loginId)) return;
// We need to jump from History to Geo
List<LoginHistory> history = \[SELECT LoginGeoId FROM LoginHistory WHERE Id = :loginId LIMIT 1\];
if (history.isEmpty() || history\[0\].LoginGeoId == null) return;
LoginGeo geo = \[SELECT City, Subdivision, Country, PostalCode
FROM LoginGeo
WHERE Id = :history\[0\].LoginGeoId
LIMIT 1\];
// Now we stamp the data onto our records
for (Account acc : records) {
acc.User\_IP\_Address\_\_c = ipAddress;
acc.User\_Location\_\_c = geo.City + ', ' + geo.Subdivision + ' ' + geo.Country;
}
}
Don't run this query inside a loop. I've seen developers drop this logic into a trigger for-loop and hit SOQL limits within minutes of going live. Fetch the session data once at the start of your service class and then reuse it.
Best practices for handling IP data
Just because you can track an Apex user IP address doesn't mean you should. Before you start stamping IP addresses on every record in your org, check with your legal team. Between GDPR and CCPA, storing PII (Personally Identifiable Information) like a specific IP can turn into a compliance headache.
Then there's choosing the right tool for the job. If you only need this on one screen, a simple LWC may be the better fit. If you need the data persisted through a complex transaction, Apex wins. Either way, bulkify the logic when you're processing multiple records at once.
Key takeaways
- Use
Auth.SessionManagement.getCurrentSession()to get the Apex user IP address. - The
LoginGeoobject is where the city and country data lives. - Always use
Test.isRunningTest()guards since sessions don't exist in test context. - Be mindful of privacy laws like GDPR before storing location data permanently.
- Check for nulls at every step, because not every login has a geo-record.
Getting the Apex user IP address is a straightforward way to add a layer of security or personalization to your org. It's a tool I keep in my back pocket for any project involving sensitive data or regional business logic. Keep your code clean, watch your limits, and respect user privacy.
Leave a Comment