Group membership expiration is the date a person should stop being a member of a group, and the removal that has to run on that date. An access review asks whether a seat is still justified. Expiration answers a narrower question that was already decided when the seat was granted: this membership ends on a known day. Reviews still cover seats that have no end date. The expiration job covers seats that have one and are still present the morning after.
Rutagon builds that job beside the GRC platform a SOC 2 team already runs. The platform can assign a reviewer and store a decision. The directory is where the member list actually lives, in Microsoft Entra, in AWS IAM Identity Center, or in both. A contractor package can end on Tuesday while a direct add from March is still on the group. We store the end date next to the membership and remove the member when the date has passed, then we read the group again.
Where group membership expiration is recorded
Three clocks show up in the directories we ingest. They are different fields, and mixing them up is how a "temporary" seat becomes permanent.
Entra entitlement management can put an end on an access package assignment. Microsoft documents that lifecycle in the access package lifecycle guidance. When the assignment expires, entitlement management removes the user from the resources in the package, including groups that were granted through the package. That removal is real, and it is limited to the edges the package created. A direct add to the same group, made later in the portal, is a second edge. The package can expire while the direct edge remains. Our ingest stores both: the package assignment id and its end time, and the direct membership, whose end time stays empty until a policy fills one in.
Privileged Identity Management for groups has its own end. An active assignment carries an end time, and the active membership drops when that window closes. An eligible assignment can remain, ready to activate again. A report that only asks "is the user in the group this morning" misses the eligible seat. We store assignment state, the end time, and whether the group is tied to an admin role.
The third clock is ours, because the directory does not have one. A group membership in the IAM Identity Center identity store has members and no expiration attribute. A security group created for a project is the same shape in Entra when someone clicked Add member. We write an end date in our store when the grant is made. Contractor seats end at 90 days. Incident seats end at 14 days. Project seats end on the date the project owner named when the grant was requested. The directory stays the source of who is a member. Our table is the source of when that membership was supposed to end.
Microsoft 365 group expiration is a different control, and it gets confused with this one often. That policy expires the group object when owners do not renew it. Owners get mail. A renewal extends the group. It does not record a decision about each member. Microsoft 365 group access review is the review of who sits in the group. Per-member end dates are the rows in this pipeline.
Memberships that outlive the date
The failure is a member who is still in the group after the end date. The report is a join.
Each morning the job lists current members from the directory and joins them to the expiration table on group id and member id. A row is late when the end time is in the past and the member is still present. The source column keeps a late package edge distinct from a late direct edge and from a late Identity Center membership.
Nested groups hide late edges. A user removed from an outer Microsoft 365 group can remain inside an inner security group, and the inner group can be the principal assigned to an AWS account or a SaaS application. Nested group access review is the expansion we already run for campaigns. The expiration job uses the same expansion, one level by default, and records the path from outer group to inner group. A late membership on the inner group is its own row with its own end date. Clearing the outer membership and marking the campaign complete leaves the inner seat untouched.
Dynamic groups are the other trap. If the rule still matches the user, a remove is rejected or the user reappears on the next evaluation. The job records the rule id and does not count the remove as success while the rule matches. The row goes to a reviewer with the rule text. Someone has to change the rule or the attribute the rule tests. An API delete against a dynamic group, with no rule change, is not a removal. A direct permission set on the same user can also remain after the group line is clear, so the morning report keeps that assignment beside the group.
The removal and the evidence row
Seats with an explicit policy are removed by the job. The policy is specific: this contractor group, 90 days, remove, notify the group owner. The call is a Graph remove-member request, or DeleteGroupMembership on the identity store. We store the request id, the time, and the member id we sent. The next morning's ingest has to show the member gone. If the member is back, the row reopens and the source column says why: dynamic rule, a second edge, or a new add after the remove.
Privileged groups are not removed on a date alone. A date can be wrong, and a break-glass seat can be intentional. Those rows become review items: group, member, end date, days late, owner. Naming the owner is the same mapping as entitlement owner mapping. A group with no owner is not auto-removed, and its item cannot close. The campaign stays open on that item until an owner is named or an admin removal is recorded with a request id.
The evidence row is small on purpose. It is one membership, one end, one action, one recheck.
group_id: a17c
member_id: 90b2
source: access_package
expires_at: 2026-10-01T00:00:00Z
still_member_after_expiry: true
action: remove_member
request_id: req-441
recheck_still_member: false The quarterly file is the set of these rows for the period: removed on time, removed late, still present, blocked by a dynamic rule, or sent to a reviewer. The GRC platform receives the closed review. This file is the expiration ledger beside it. A removal sample is the time the member disappeared from the directory, tied to the request id, which is a stronger artifact than a "revoke" click with no API result.
Security automation is where we run the ingest, the morning join, and the remove call. A spreadsheet of end dates that is never joined back to the live member list is a list of intentions. The join is the control.
What the reviewer still decides
Expiration clears the seats whose end was already agreed. The reviewer's queue should be the remainder: seats with no end date, privileged seats the job will not remove, dynamic-group rows a rule still explains, and direct edges that survived a package. A queue that still contains every contractor the policy already described is the queue people stop opening.
When a reviewer chooses maintain, the item requires a new end date before it can close. Maintain with an empty end date is how a 90-day contractor seat becomes a permanent member of a group named Contractors. The new date is written to our table first. The next morning's join uses that date.
When a reviewer chooses revoke, the same remove call runs. The evidence row stores the reviewer, the decision time, and the request id. A revoke with no request id stays open. We do not export it as completed removal.
Owners who renew a Microsoft 365 group in the admin center have renewed the group object. We do not copy that renewal onto every member row. Another 90 days for a specific person is a new end date on that membership, named by a person, stored on the row.
Rutagon delivers the table, the join, the remove, and the recheck. The directory remains the record of membership. Our store is the record of the end date the directory does not keep, and of the proof that a late member was actually removed.
Frequently Asked Questions
Does every group store an end date on each member?
An access package assignment and a PIM active assignment can carry an end time. A direct member add in Entra, and a group membership in the IAM Identity Center identity store, generally do not. We write an end date beside those memberships when the grant is created, and the morning job compares that date with the live member list.
What if a dynamic rule puts the user back?
The remove is not counted as success. The row keeps the rule id and goes to a reviewer with the rule text. Someone changes the rule or the attribute it matches. The next morning's ingest checks the member again and reopens the row if the user is present.
Is a Microsoft 365 group expiration policy the same thing?
That policy expires the group object when owners do not renew it. A renewal extends the group and leaves the member list as it was. Per-member end dates are a separate record. A group renewal does not fill them in, and we do not treat the renewal mail as a completed access review.
What does a successful removal prove?
The row stores the group, the member, the end date, the source edge, the API request id, and the next morning's recheck. A row marked removed is one whose recheck showed the member gone. The quarterly file is those rows for the seats that ended in the period.
Do reviewers still see contractor seats that have a policy end date?
They see the exceptions: no owner, a privileged group, a dynamic rule, a direct edge that survived a package, or a maintain decision that needs a new end date. Seats the policy already told the job to remove are removed and recorded, so the queue is the decisions that were not made when the seat was granted.
Ask for an access-review diagnostic that joins group end dates to live membership. Contact Rutagon at rutagon.com/contact, 907-841-8407, or contact@rutagon.com.