skip to content

A SAML assertion's `<AuthnStatement>` carries AuthnInstant and SessionNotOnOrAfter — what has the identity provider stated, and what has it not?

level: seniorimportance: should knowfreq 33%

answer

  1. a statement about a past event
  2. IssueInstant and AuthnInstant differ
  3. a session answer carries an old AuthnInstant
  4. SessionNotOnOrAfter bounds the issuer's session
  5. AuthnContextClassRef is claimed, not demonstrated

basics

~20 s

AuthnInstant states when the subject authenticated to the identity provider, which may be hours before this assertion was issued; SessionNotOnOrAfter bounds the session with that identity provider. Neither says the subject was present at the moment the statement was made.

solid answer

~40 s

An `<AuthnStatement>` is a statement about a past event. `AuthnInstant` is when the subject authenticated to the identity provider — not when the assertion was made, which is the envelope's `IssueInstant`. An identity provider answering from an existing session will happily issue a fresh assertion whose `AuthnInstant` is hours old, and that is correct behaviour, not a defect. `SessionNotOnOrAfter` bounds the session between the subject and that identity provider; it does not by itself end the reading party's own session, which is a separate lifetime the reader chooses. `SessionIndex` names that identity-provider session so it can be referred to later. `<AuthnContextClassRef>` states the class of authentication used, as a URI — an assertion by the issuer, not evidence the reader can verify from the document.

code

xml · 11 lines
xml
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                ID="_8f3a2c91" IssueInstant="2026-09-19T08:14:02Z" Version="2.0">
  <!-- the document is two and a half hours younger than the event it reports -->
  <saml:AuthnStatement AuthnInstant="2026-09-19T05:40:11Z"
                       SessionIndex="_s77c2"
                       SessionNotOnOrAfter="2026-09-19T17:40:11Z">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
</saml:Assertion>

go deeper

for a junior

Know that the AuthnStatement reports when the subject authenticated, and that this time is not the same as when the assertion itself was created.

for a middle

Explain how an identity provider answering from an existing session produces a new assertion with an old AuthnInstant, and why that is correct rather than stale data.

for a senior

Show the operational consequences: an audit trail that stamps its own clock answers the wrong question, and ignoring the stated session end leaves two systems disagreeing about a signed-out subject.

for a principal

Own the policy: how old an authentication you will accept for which operations, and whether your session lifetime is bound to a counterparty's stated session end.

## Three instants, three different meanings An assertion carrying an `<AuthnStatement>` has at least three times in it, and conflating any two of them produces a wrong answer that sounds fine: 1. **`IssueInstant`** on `<saml:Assertion>` — when this document was created. 2. **`AuthnInstant`** on `<saml:AuthnStatement>` — when the subject authenticated to the identity provider. 3. **`SessionNotOnOrAfter`** on the same element — when the session between the subject and that identity provider is stated to end. The second is the one that surprises people. Take the grain co-operative: a haulier's driver signs in at the depot terminal at 05:40. At 08:14 they arrive at the co-operative's weighbridge, the intake system sends them to the haulier's identity provider, and an assertion comes back. Its `IssueInstant` is 08:14; its `AuthnInstant` is **05:40**. The identity provider still had a session for that driver and answered from it. Nothing is broken — the statement is accurate. It says *this subject authenticated at 05:40*, and that is all it ever said. ## What the statement therefore proves - That the issuer, at `IssueInstant`, was willing to state that the subject had authenticated at `AuthnInstant`. - That at the time of issue the issuer regarded a session with that subject as running, named by `SessionIndex`. ## What it does not prove - **That the subject was present just now.** Between `AuthnInstant` and `IssueInstant` anything may have happened to the device the session lives on. A statement about a past authentication is not a liveness check. - **That the subject authenticated strongly.** `<AuthnContextClassRef>` is a URI the issuer chose to put in the document. It is a claim about how the authentication was done, and a reader that relies on it is relying on the issuer's word, which is the whole basis of federation but should be said out loud. - **That the reader's own session must end at `SessionNotOnOrAfter`.** That attribute is about the session with the identity provider. The reading system's session is its own object with its own lifetime. If what you actually need is *fresh* authentication rather than a statement about an old one, that is a property of the request that asked for the assertion, not something you can extract from the statement in front of you. | Field | What it states | Whose thing it is | |---|---|---| | `IssueInstant` | when the document was made | the assertion envelope | | `AuthnInstant` | when the subject authenticated | the identity provider's record of a past event | | `SessionIndex` | which session this statement belongs to | the identity provider's session | | `SessionNotOnOrAfter` | when that session is stated to end | the identity provider's session | | `<AuthnContextClassRef>` | the class of authentication used | the issuer's claim | ## The production shape of the mistake Two failures come out of reading these fields as more than they are. The first is an audit problem. The intake system records "driver authenticated at 08:14" because it stamped its own clock instead of reading `AuthnInstant`. Months later, a dispute over a load turns on when that driver actually proved who they were, and the record answers a different question from the one being asked. The second is a session-lifetime problem. A reader that ignores `SessionNotOnOrAfter` entirely keeps its local session running for its own default — which may be a working day — while the identity provider considers the subject's session over. The two systems then disagree about a subject who is, from the identity provider's point of view, no longer signed in anywhere. The fix is a decision, not a field: you choose whether your local session is bounded by the issuer's stated session end, and you say so. ## Reading `AuthnInstant` honestly The useful habit is to treat `AuthnInstant` as **evidence with an age**. It is a real fact about a real event, and the older it is relative to `IssueInstant`, the less it tells you about the person standing at the weighbridge now. A gap of seconds means the subject just authenticated. A gap of three hours means the identity provider answered from a session, and whether that is acceptable is a policy question your side owns. ## What to take away - `IssueInstant`, `AuthnInstant` and `SessionNotOnOrAfter` are three different clocks, on two different elements. - A fresh assertion may legitimately carry an old `AuthnInstant`. - `SessionIndex` names the issuer's session; it does not name yours. - `<AuthnContextClassRef>` is asserted, not demonstrated. - Whether your own session ends when the issuer's does is your decision, and it should be a deliberate one.

  • An assertion's `IssueInstant` is 08:14 and its `AuthnInstant` is 05:40. Is that a defect?
    No. The identity provider had a running session for that subject and answered from it, so the statement reports the authentication event it actually has. Whether a two-and-a-half-hour-old authentication is good enough for the weighbridge is a policy decision on the reading side, and asking for a fresh one is a property of the request, not of this document.
  • Does `SessionNotOnOrAfter` tell a service provider when to end its own session?
    It tells it when the session with the **identity provider** is stated to end. The reading party's session is a separate object with its own lifetime, and the specification does not make one end the other. Bounding your local session by that instant is a sensible, deliberate choice — but it is a choice you make, not a rule the attribute enforces.
  • What does `<AuthnContextClassRef>` let a reader conclude about how the subject authenticated?
    Only what the issuer asserted. It is a URI naming a class of authentication method, placed in the document by the party that performed the authentication. There is nothing in the assertion a reader can check it against, so relying on it is relying on the issuer — which is the basis of federation, but is worth stating rather than assuming.

saying these in an interview costs you the question

  • Reads AuthnInstant as the moment the assertion was issued.
  • Calls an old AuthnInstant on a fresh assertion a defect.
  • Thinks SessionNotOnOrAfter ends the reading party's own session.
  • Treats AuthnContextClassRef as verified evidence rather than a claim.
  • Says SessionIndex identifies the subject rather than a session.
  • Assumes every AuthnStatement carries SessionIndex and SessionNotOnOrAfter.