Certifications
Access certification campaigns where reviewers or managers keep or revoke each binding, with reminders, usage-based recommendations, and auto-close.
Regulations and customers often require proof that someone regularly checks who has access to what, and removes what is no longer needed. Doing that in a spreadsheet is slow, and the decisions rarely make it back into the system.
An access turns the review into a recorded decision. It takes a snapshot of the role under review and asks reviewers to keep or revoke each one. When it closes, it applies the result: revoked bindings are removed and every item records what happened. Reviewers can be named people or each person's manager, recommendations put usage evidence beside every item, and campaigns can close themselves when they are due.
Create a campaign
Open one campaign per review cycle, such as a quarterly review of the administrator roles.
certifications.create opens it and needs iam:certifications:manage. It returns the campaign with items, the
number of bindings it covers.
const campaign = await iam.api.certifications.create(credential, {
tenantId,
name: 'Q3 admin review',
roleIds: [admin.id, billingAdmin.id],
reviewerMode: 'manager',
reviewerIds: [securityLead.id], // items without an active manager go here
dueAt: Date.parse('2026-10-15T17:00:00Z'),
autoClose: true,
undecided: 'revoke',
});
// campaign.items: how many bindings the campaign coversProp
Type
The campaign snapshots the live bindings of every non-protected role, or of the listed roles and subject type.
It covers at most 5000 bindings (LIMIT_EXCEEDED otherwise; narrow it by role or subject type). Each item records
the role, the subject, the binding's end, and whether it is eligible.
When email delivery is configured, each reviewer receives a certification-review email so they know they have
work. It is queued in the campaign's transaction and carries campaignId, campaignName, items (their own item
count), and dueAt when set.
Decide
Reviewers go through their items and record keep or revoke, optionally with a note that explains the decision.
certifications.decide records decisions in batches of 1 to 200 items and returns how many it recorded:
await iam.api.certifications.decide(reviewerCredential, {
tenantId,
campaignId: campaign.id,
decisions: [
{ itemId: 'item-1', decision: 'keep' },
{ itemId: 'item-2', decision: 'revoke', note: 'Moved to finance in July' },
],
});decideneedsiam:certifications:review. When the campaign namesreviewerIds, only they may decide.- Nobody decides on their own access, directly or through a group (
SELF_REVIEW), because a review of yourself is no review. - A decision can be changed until the campaign closes; a closed campaign refuses decisions (
CONFLICT).
To follow progress, certifications.list lists campaigns (optionally by status) and certifications.get returns
one campaign with its items. With mine: true, get leaves out items about the caller's own access, which they
may not decide. Both report progress (total, decided, keep, revoke) and need iam:certifications:read.
Manager reviews
The best reviewer for a person's access is usually their manager. With reviewerMode: 'manager', each person's
items go to their active manager (Identity.managerId, which SCIM can maintain). Items without one, and group
items, go to the named reviewers, or to anyone holding iam:certifications:review when none are named.
Managers do not need an administrator role for this:
certifications.listMine({ tenantId })returns the open campaigns with items assigned to the caller, which is all a manager's review screen needs.certifications.review({ tenantId, campaignId, decisions })records the manager's decisions. It needs no certification permission, only an ordinary session of the tenant (not ) and the assignment, and is audited ascertification:review.- Through
decide, a manager-assigned item may be decided only by that manager or by a holder ofiam:certifications:manage. - Each reviewer is emailed once with their own item count.
Recommendations
Reviewers approve almost everything when they have nothing to go on. Recommendations give them evidence.
roleMining.reviewRecommendations({ tenantId, campaignId, unusedDays? }) (iam:analysis:read) suggests a
decision for every item, with a reason:
const { usageComplete, recommendations } = await iam.api.roleMining.reviewRecommendations(credential, {
tenantId,
campaignId: campaign.id,
});
// recommendations: [{ itemId, recommendation: 'keep' | 'revoke' | 'none', basis, reason, lastUsedAt }]revokewhen the account is disabled, deleted, or expired, or when the person did not use the role withinunusedDays(90 by default);keepwhen they did.- The evidence (
basis) is recorded access usage once it covers the whole window (usageComplete), otherwise the person's last sign-in, orstatusfor an inactive account. - Group items and service accounts without usage data get
none.
Reviewers still decide. The console's campaign page shows the suggestion and its reason next to each open item.
Remind
Reviews stall when reviewers forget. certifications.remind({ tenantId, campaignId }) (iam:certifications:manage)
emails every reviewer who still has undecided items a certification-reminder with their pending count, and
returns { reminded, pending }. Send one a few days before dueAt. It needs an email delivery callback
(DELIVERY_REQUIRED otherwise), refuses closed campaigns, and is audited as certification:remind.
renderDeliveryMessage from better-iam/auth/templates, the built-in email renderer, renders both the
certification-review and certification-reminder emails. Give it a links.certification({ tenantId, campaignId })
function that returns your campaign page URL, and the emails get a button to it.
Close
Closing is what makes the review count. certifications.close({ tenantId, campaignId })
(iam:certifications:manage, recent authentication) applies the campaign:
- Revoked items, and undecided ones when
undecided: 'revoke', are removed under the closer's . Each revocation is audited asiam:bindings:delete. - Every item records its outcome:
kept,revoked,already-removed(the binding was gone already), orrevocation-failed(a binding a higher authority granted, left for that administrator). The result carries the counts per outcome. - Enforced (change safety) guard closing a campaign like any other access change.
Closed campaigns keep their record, with every decision and outcome, as evidence for auditors.
certifications.delete removes a campaign and its items once you no longer need it; it only accepts closed
campaigns.
Auto-close
A campaign with autoClose: true (and dueAt) closes itself when it is due. The deployment job
iam.closeOverdueCertifications({ tenantId? }), or CLI close-certifications, finds every due campaign and
applies it:
- Each campaign closes in its own transaction, under the creator's grant authority. Revocations the creator could
not make, or every revocation when the creator no longer exists, are reported as
revocation-failed. - Each close is audited as
certification:auto-close(with the outcome counts) bydeployment-operator, plus oneiam:bindings:deleteper removed binding. - It returns
{ closed, skipped }, whereskippedcounts auto-closing campaigns that are not due yet. It needs no credential and no email transport.
better-iam close-certifications --config better-iam.config.mjsEnforced invariants do not guard scheduled jobs such as auto-closing campaigns; the invariant monitor reports what they break.
Better IAM is created by Sean Filimon
Last updated