{"record":{"id":"93fa77bb4dda3b2a","repo":"apereo/cas","slug":"unable-to-login-from-this-location","errorCode":null,"errorMessage":"Unable to login from this location","messagePattern":"Unable to login from this location","errorType":"exception","errorClass":"InvalidLoginLocationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-generic/src/main/java/org/apereo/cas/adaptors/generic/JsonResourceAuthenticationHandler.java","lineNumber":84,"sourceCode":"        val account = map.get(username);\n        if (!matches(originalPassword, account.getPassword())) {\n            LOGGER.warn(\"Account password on file does not match the provided password for [{}]\", username);\n            throw new FailedLoginException();\n        }\n\n        LOGGER.debug(\"Located account [{}]\", account);\n        switch (account.getStatus()) {\n            case DISABLED -> throw new AccountDisabledException();\n            case EXPIRED -> throw new AccountExpiredException();\n            case LOCKED -> throw new AccountLockedException();\n            case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();\n            case OK -> LOGGER.debug(\"Account status is OK\");\n        }\n\n        val clientInfo = ClientInfoHolder.getClientInfo();\n        if (clientInfo != null && StringUtils.isNotBlank(account.getLocation())\n            && !RegexUtils.find(account.getLocation(), clientInfo.getClientIpAddress())) {\n            throw new InvalidLoginLocationException(\"Unable to login from this location\");\n        }\n\n        if (StringUtils.isNotBlank(account.getAvailability())) {\n            val range = Splitter.on(\"~\").splitToList(account.getAvailability());\n            val startDate = DateTimeUtils.convertToZonedDateTime(range.getFirst());\n            val endDate = DateTimeUtils.convertToZonedDateTime(range.get(1));\n            val now = ZonedDateTime.now(Clock.systemUTC());\n            if (now.isBefore(startDate) || now.isAfter(endDate)) {\n                throw new InvalidLoginTimeException(\"Unable to login at this time\");\n            }\n        }\n\n        val warnings = new ArrayList<MessageDescriptor>();\n        if (account.getExpirationDate() != null) {\n            val now = LocalDate.now(ZoneOffset.UTC);\n            if (now.isEqual(account.getExpirationDate()) || now.isAfter(account.getExpirationDate())) {\n                throw new AccountExpiredException();\n            }","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-generic/src/main/java/org/apereo/cas/adaptors/generic/JsonResourceAuthenticationHandler.java#L66-L102","documentation":"JsonResourceAuthenticationHandler reads account records from a JSON resource that may carry a 'location' regex per account. When ClientInfoHolder has client info and the account's location pattern does not match the requester's IP, it throws InvalidLoginLocationException 'Unable to login from this location', blocking authentication based on network origin.","triggerScenarios":"authenticateUsernamePasswordInternal for a JSON account whose location field is non-blank and fails RegexUtils.find(account.getLocation(), clientIpAddress).","commonSituations":"Account restricted to an office/VPN subnet but user connects from a different network; IPv6 vs IPv4 mismatch against a regex written for the other family; location regex written without correct escaping; NAT/proxy making the client IP different from expected.","solutions":["Update the account's location regex in the JSON resource to include the client's current IP/subnet","Check ClientInfoHolder-reported IP (proxy headers) matches what the regex expects","Make the regex family-agnostic if both IPv4/IPv6 are expected (e.g. 10\\..*|fc00:.* )","Remove/blank the location field if IP restriction is not intended"],"exampleFix":"// before (JSON account)\n\"location\": \"^192\\\\.168\\\\.1\\\\.10$\"\n// after\n\"location\": \"^192\\\\.168\\\\.1\\\\.(10|1[1-9])$\"","handlingStrategy":"try-catch","validationCode":"String ip = ClientInfoHolder.getClientInfo() != null ? ClientInfoHolder.getClientInfo().getClientIpAddress() : null;\n// precheck account location regex against ip before submitting","typeGuard":null,"tryCatchPattern":"try {\n    return handler.authenticate(credential);\n} catch (InvalidLoginLocationException e) {\n    // respond 403 with location-based denial message\n}","preventionTips":["Maintain location regexes for VPN/office ranges including both IPv4 and IPv6 forms","Document IP restrictions per account in the JSON resource","Account for proxies/NAT when authoring IP rules"],"tags":["authentication","ip-restriction","json-resource","access-control"],"backgroundTag":"permission-denied","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}