Skip to content
AccessLens

Tutorials

Travel Exemptions in Conditional Access Without the Stale Policy Pile-Up

How to handle international travel tickets without editing CAPs every week or leaving a trail of stale named locations. A time-bound exemption group with PIM or access packages does the work for you.

Stephen Devlin · · 6 min read

Every tenant I have worked in that geo-blocks to a home country eventually ends up with the same problem. Someone messages the helpdesk on a Friday to say they are flying to Spain on Sunday, and you are now either editing a named location or carving out a one-off exclusion on a CAP at 5pm. Do that for a year and you end up with a named locations list that includes half of Europe and an exclusion list nobody can audit.

I have seen this fall apart in tenants of every size, and the fix is genuinely not that hard. You just have to stop reaching for the policy editor.

The Two Mistakes Nearly Everyone Makes First

When the first travel ticket lands, the admin usually does one of two things. Either they add the destination country to the trusted named location, or they edit the CAP to exclude that user.

Both feel reasonable in the moment. Both age badly.

Adding Spain to your trusted named locations means Spain is now trusted for the entire workforce. You wanted to let one person check email from Madrid for a week. Six months later Spain is still in there, and so are Italy, Portugal, the UAE, and that one weird entry from when a director went to Singapore.

Editing the CAP to exclude individual users is worse. Every trip adds another name to the exclusion list. Nobody removes them when the trip ends because nobody is sure if the trip ended. Eventually the exclusion list is longer than the include list and the policy is doing nothing useful for half the company.

This is the classic shape of CA drift. Quiet, gradual, and impossible to undo without a long afternoon and a spreadsheet.

What Actually Works

The pattern I land on every time is the same. Leave the CAP and the named location alone. Manage exceptions through a dedicated group with time-bound membership.

It looks like this:

Conditional Access Policy · Static
Geo-Block CAP
Include
All users
Exclude
CA-Travel-Exemption group
Condition
Location not in Trusted Countries
Grant Control
Block
When a Travel Ticket Arrives
01
Trigger
User raises travel ticket
↓
02
Action
Time-bound PIM membership of CA-Travel-Exemption
↓
03
Active Window
CAP excludes user, geo-block does not apply
↓
04
Auto-Cleanup
Membership expires, geo-block applies again

The CAP does not change. The named location does not change. The only thing that moves is group membership, and the membership cleans itself up.

That last part is the whole point. If your exemption mechanism does not include an automatic expiry, you are just creating a slower version of the same problem.

Doing It Properly with PIM for Groups

If you have Entra ID P2 (or an Entra ID Governance licence), PIM for Groups is the cleanest path. You create the exemption group, onboard it to PIM, and then assign users as eligible rather than active.

When a travel ticket lands, the user (or the helpdesk on their behalf) activates the membership for the duration of the trip. Once the activation window closes, they are back to eligible-but-not-active and the geo-block applies again automatically.

The settings I usually recommend on the activation policy:

  • Maximum duration: 14 days, sometimes 7 if the org is tighter
  • Justification: required
  • Ticket information: required
  • Approval: depends on your governance posture, but I lean towards yes for anything over 7 days
  • Notification: send to a security DL so somebody at least sees it land

Through Graph, an assignment request looks like this:

POST https://graph.microsoft.com/v1.0/identityGovernance/privilegedAccess/group/assignmentScheduleRequests
Content-Type: application/json

{
  "accessId": "member",
  "principalId": "<user-object-id>",
  "groupId": "<exemption-group-id>",
  "action": "adminAssign",
  "scheduleInfo": {
    "startDateTime": "2026-05-15T00:00:00Z",
    "expiration": {
      "type": "afterDateTime",
      "endDateTime": "2026-05-22T00:00:00Z"
    }
  },
  "justification": "Travel exemption for ticket INC0012345"
}

Expires on its own at the end of the window. No reminder ticket, no cleanup task, no PowerShell on a schedule. That is the win.

Two constraints on the group itself worth knowing about. PIM for Groups does not support dynamic membership groups, and it does not support groups synchronised from on-premises Active Directory. The group has to be a cloud-only security group or Microsoft 365 group with assigned membership. If you try to onboard a dynamic group, the option simply will not appear.

One thing that often comes up is whether to mark the group role-assignable. Until January 2023, this was required for PIM. It is no longer. You can onboard a regular security group and it works fine. That said, role-assignable groups get extra protections (only Global Admin, Privileged Role Admin, or the group Owner can manage them) which is a fair argument for using one anyway if the exemption is sensitive. The trade-off is that the role-assignable flag is set at creation time and cannot be toggled on later, so if you want it, decide up front.

If You Are on P1

Not everyone has P2 and that is fine. The same shape still works.

The cleanest non-PIM option is Entra ID Governance access packages. You package up membership of the exemption group, attach a lifecycle policy that expires the assignment after a set period, and optionally publish it to a catalogue so users can request it themselves with manager approval. The audit trail is built in and you do not have to maintain anything.

If you have neither PIM nor Governance licensing, you fall back to a Logic App or a scheduled runbook. Something like:

# Remove expired travel exemptions
$group = Get-MgGroup -Filter "displayName eq 'CA-Travel-Exemption'"
$members = Get-MgGroupMember -GroupId $group.Id -All

foreach ($member in $members) {
    $endDate = (Get-MgUser -UserId $member.Id -Property "extension_TravelExemptionEnd").AdditionalProperties["extension_TravelExemptionEnd"]
    if ($endDate -and ([datetime]$endDate) -lt (Get-Date)) {
        Remove-MgGroupMemberByRef -GroupId $group.Id -DirectoryObjectId $member.Id
        Write-Output "Removed $($member.AdditionalProperties.userPrincipalName), exemption ended $endDate"
    }
}

This works. I do not love it. The failure mode is silent. If the runbook stops running for a week, nobody notices because nothing visibly breaks, and the exemptions just sit there. Treat it as a stopgap until you can justify P2.

What You Should Actually Exempt

A few things I have learned the hard way here.

Do not exempt the user from your geo-block CAP without thinking about what else they should have to satisfy while travelling. A user abroad probably warrants a slightly stricter posture, not a weaker one. The pattern I prefer is to combine the exclusion on the geo-block CAP with a second CAP that targets the exemption group and requires MFA plus a compliant device. They are off the geo-block, but on the hook for everything else.

Do not drop the exemption group into broad exclusion lists across multiple policies. Scope it tightly. One CAP exclusion, ideally. The minute it starts appearing in three or four policies, you have lost track of what activation actually allows.

Do not exempt service accounts. They should not be travelling. If you see a service account land in this group, something is wrong upstream and you want to know about it.

Knowing Who Is Actually Out There

Even with time-bound membership doing the cleanup, you want to be able to answer "who is currently abroad" without scrolling through group membership.

A quick KQL query against the audit logs surfaces recent activations:

AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName == "Add member to group"
| where TargetResources has "CA-Travel-Exemption"
| project TimeGenerated, InitiatedBy, TargetResources, AdditionalDetails
| sort by TimeGenerated desc

And for the sign-ins themselves, where the exemption actually got used:

SigninLogs
| where TimeGenerated > ago(14d)
| where ConditionalAccessStatus == "success"
| extend CA = parse_json(ConditionalAccessPolicies)
| mv-expand CA
| where CA.displayName has "Geo-block" and CA.result == "notApplied"
| project TimeGenerated, UserPrincipalName, Location, IPAddress, AppDisplayName
| sort by TimeGenerated desc

If somebody pops up here from a country you were not expecting, that is the signal. Much easier than reverse-engineering it from named location change history six weeks later.

Things to Avoid

A few traps I have walked into or watched other people walk into.

Trying to onboard a dynamic group. PIM for Groups does not support dynamic membership at all. The option will not appear in the UI. Even if it did, dynamic membership defeats the entire point. The whole reason this works is that membership is a deliberate, time-bound act.

Trying to onboard an on-prem-synced group. Same blocker. PIM for Groups only works on cloud-only groups. If your CA-Travel-Exemption group is synced from AD, you will need to create a fresh cloud group.

Putting the exemption group in too many CAPs. Tight scope or you lose control of what activation actually unlocks.

Same person requesting and approving. If the helpdesk can both ask for and grant their own travel exemption, the approval is theatre. Route it to a security reviewer or use auto-approval with mandatory notification so at least somebody sees it happen.

Skipping the second CAP. If you only exclude users from the geo-block and do not add anything in return, your travelling users end up with a weaker posture than your stationary ones. That is the wrong direction.

Wrap-Up

Travel exemptions look like a tiny problem until you have done a year of them. The named locations list bloats, the helpdesk burns hours, and you cannot answer basic questions about who has access from where any more.

A dedicated exemption group with time-bound membership solves the lot. The policy never changes, the named location never changes, and the exemption expires on its own. PIM for Groups is the most direct route if you have P2. Access packages are the right answer on Governance. PowerShell is the stopgap if you have neither.

The bigger principle is that recurring exceptions should be modelled as state with an expiry date, not as policy edits. Once you internalise that, the named locations list stops being something you dread opening.

Sources

Share this post
LinkedIn