Access lifecycle
Time-bound identities, temporary memberships, future-dated bindings, API key hygiene, and offboarding that removes all access in one call.
Access should end when the reason for it ends: a contract, a project, an integration, a job. In most systems it ends only when someone remembers to remove it, which is why old accounts and keys pile up. Better IAM lets you put the end date on the access itself when you grant it, shows you what nobody uses, and gives you one call that removes everything when a person leaves.
Time-bound identities
Contractors, interns, auditors, and temporary integrations have a known last day. Instead of a calendar reminder
to disable them, give the identity a deadline: expiresAt on identities.create or identities.update for
people, and on serviceAccounts.create or serviceAccounts.update for
.
await iam.api.identities.create(credential, {
tenantId,
email: 'contractor@example.com',
name: 'Contractor',
expiresAt: Date.parse('2027-03-31T00:00:00Z'),
});
// Who deactivates in the next 30 days?
const expiring = await iam.api.identities.list(credential, {
tenantId,
expiresBefore: Date.now() + 30 * 86400_000,
});
// The contract was extended: move the deadline, or clear it with null.
await iam.api.identities.update(credential, { tenantId, identityId, expiresAt: null });What happens at the deadline:
- Immediately. Every credential of the identity is refused from
expiresAton, even before any job runs. - At the next purge. The retention worker,
iam.purgeDeleted()(CLIpurge), disables the identity, revokes its sessions, and ends its role . It recordsidentity:expirewith thekindandexpiresAt(actordeployment-operator). Schedule it nightly; see scheduling. - Before it happens.
identities.list({ expiresBefore })lists identities that end before a date, the access report shows them to administrators, and expiry reminders warn the person a week ahead.
Clearing or extending the deadline is the only way to re-enable an expired identity, so nobody quietly turns a contractor back on. Changing the deadline of an owner or a root administrator needs the same protection as disabling them, and moving a deadline into the past is refused: disable the identity instead.
Temporary and future-dated grants
The same idea works for individual grants, when the person stays but a piece of their access should not. These are covered in detail under temporary access; in short:
- Temporary .
bindings.create({ ..., expiresAt })grants a role until a date (in the future, within ten years).bindings.updateextends, shortens, or clears the end withnull. - Future-dated bindings.
startsAtmakes a binding begin on a date. It is listed with its start but grants nothing until then, so you can set up a new hire's access before their first day. - Temporary memberships.
groups.addMember({ ..., expiresAt })makes a group membership end on a date, and with it every grant and activation it carried.groups.updateMemberextends, shortens, or clears the end, and re-adding a lapsed member renews the membership. - Packages. An assignment with an end date ends every binding and membership it created at that time. See access packages.
// A three-month project membership that ends by itself.
await iam.api.groups.addMember(credential, {
tenantId,
groupId: projectTeam.id,
identityId: alice.id,
expiresAt: Date.now() + 90 * 86400_000,
});
// Access that begins on the contract's first day and ends on its last.
await iam.api.bindings.create(credential, {
tenantId,
roleId: contractor.id,
subjectType: 'identity',
subjectId: bob.id,
startsAt: Date.parse('2027-01-04T08:00:00Z'),
expiresAt: Date.parse('2027-06-30T18:00:00Z'),
});Expired grants stop applying at their end. The purge worker then deletes expired bindings, lapsed memberships,
ended activations, and package assignments past their end, and marks pending access requests past their
lifetime as expired, reporting each count (expiredBindings, expiredMemberships, and so on).
API key hygiene
API keys let integrations call Better IAM as a service account (an identity for software rather than a person). Keys are easy to issue and easy to forget: after a year, nobody knows which ones are still used or what they may do. Three properties keep them reviewable:
- Labels. A key carries a
name(up to 128 characters) and adescription(up to 512), so a review can tell what each one is for. - Last use. A key records
lastUsedAtwhen it authenticates a request (at most once a minute).credentials.list({ unusedForMs })returns keys nobody used in that long, including keys never used since they were issued, so you can find the ones to revoke. - Scopes.
scopesis an allowlist of actions. It is compiled into a session policy that allows exactly those actions on every resource, so an integration never holds more than it needs, whatever roles its service account has. Pass eitherscopesor a fullpolicy, not both.
const key = await iam.api.credentials.create(credential, {
tenantId,
identityId: deployer.id,
name: 'github-actions',
description: 'Deploys from the release workflow',
scopes: ['deployments:create', 'deployments:read'],
expiresInSeconds: 90 * 86400,
});
// key.token is returned once: store it in the CI secret store now.
// Keys nobody has used for 30 days, including keys never used since they were issued.
const unused = await iam.api.credentials.list(credential, {
tenantId,
unusedForMs: 30 * 86400_000,
});
for (const item of unused)
await iam.api.credentials.revoke(credential, { tenantId, credentialId: item.id });The key calls:
credentials.createissues a key for an active, unexpired service account (INVALID_IDENTITYotherwise). Keys expire after 90 days by default (expiresInSeconds, from one minute to one year). It needs recent authentication.credentials.listlists keys with their labels, scopes, and last use; token material is never returned.credentials.updaterelabels a key or moves its expiry (into the future, at most a year out) without changing the token. Moving the expiry needs recent authentication and the right to use the key's issuing authority.credentials.rotateissues a new token for the key and invalidates the old one. It keeps the label and expiry but resets the usage history, so a rotated key shows up as unused until it is put to work.credentials.revokedeletes a key at once. It needs recent authentication.
The access report lists unused and expiring keys, and the access
analysis reports keys unused for its dormancy window as stale-api-key (see
access reviews).
Offboarding
When someone leaves, their access is spread across role bindings, groups, packages, sessions, keys, relationships,
and delegated authority. Removing it piece by piece is slow and easy to get wrong, and the things they owned still
need an owner. identities.offboard does it in one transaction: it disables the person or service account,
removes everything that granted them access, and hands what they owned to a successor.
const summary = await iam.api.identities.offboard(credential, {
tenantId,
identityId: leaver.id,
reason: 'Left the company (HR-1234)',
successorId: manager.id, // takes over the resources and reports the leaver had
});
// summary: { identity, sessions, bindings, memberships, activations, packages, relationships,
// accessRequests, authorities, resourcesReassigned, resourcesOwned, reportsReassigned }
// Later, after your retention period, remove the record itself:
await iam.api.identities.delete(credential, { tenantId, identityId: leaver.id });In that one transaction the call:
- Removes ownership (the protected Owner binding) when the identity is an owner.
- Ends its role activations.
- Revokes its access package assignments, with the bindings and memberships they created, including automatic ones.
- Deletes its remaining role bindings (under the caller's authority, like
bindings.delete) and group memberships. - Deletes its relationships, and cancels its pending access requests and package requests.
- Revokes the it holds, so grants it issued as a delegated administrator stop applying.
- Moves its reports to the successor, or leaves them without a manager when there is no successor. A successor who reported to the leaver moves up to the leaver's own manager, and a report that would close a reporting loop is left without a manager.
- Transfers ownership of the managed resources it owns to the successor (
resourcesReassigned), or reports them asresourcesOwnedwhen there is no successor. - Ends every session and API key of the identity.
- Disables the identity and records
identity:offboardwith thereason,kind,successorId, and the counts.
The result counts everything it removed, which makes a good offboarding record for auditors.
The rules:
- It needs
iam:identities:updateand recent authentication.reasonis required (up to 512 characters). - Nobody can offboard themselves. Root administrators are protected, the last owner is protected, and only an owner or root can offboard an owner.
- The successor must be another active identity of the tenant.
- The identity stays as a disabled record, so the audit trail still names who they were.
identities.deletetombstones it later, after your retention period.
The console offers offboarding on the member page. Package rules never assign anything to a disabled identity, so a leaver who still matches a rule does not get access back.
Transfer package rules first
Revoking a leaver's grant authorities also suspends any package rule they own. Have another administrator take over their rules before offboarding them.
Better IAM is created by Sean Filimon
Last updated
Just-in-time elevation
Eligible bindings that people activate for a bounded time, with justification, MFA, approval, approver groups, and tenant-wide floors.
Access packages
Bundle roles and group memberships into packages that administrators assign, members request with approval, and rules grant automatically.