TT Lab
Get started
Learn Learning paths Courses

Enterprise Authentication Integration

OIDC and SAML — What You Must Verify to Be Safe

Continue in TT Lab

Summary

Whether OIDC or SAML, what makes it safe is not the protocol but the receiving side's verification list — if even one of signature, iss, aud, exp, and nonce is missing, an attacker can get in through that hole.

Why this is a problem

Integration works perfectly in the normal flow even if you leave out verification. Login works, the screen appears, and the user name comes out right. So there is no signal during development or in integration tests.

There are only two moments when a missing verification shows up — when you get a security assessment, or when an incident occurs. If you do not check the signature, anyone can tamper with the payload and get in as any user, and if you do not check aud, a token issued for another client works in our app. This is not a problem of implementation difficulty but of whether you know the list.

The OIDC Authorization Code flow

This is the standard flow for web applications that have a browser. It has five steps.

1. 사용자가 우리 앱의 보호된 페이지 요청
2. 앱 → 브라우저를 IdP 로 리다이렉트
   GET /authorize?response_type=code&client_id=labhub-web
                 &redirect_uri=https://app.example.com/callback
                 &scope=openid profile email
                 &state=<임의값>&nonce=<임의값>
3. IdP 가 로그인 처리 후 브라우저를 우리 앱으로 리다이렉트
   302 Location: https://app.example.com/callback?code=<인가코드>&state=<임의값>
4. 앱(서버)이 백채널로 토큰 교환
   POST /token  grant_type=authorization_code&code=...&redirect_uri=...
   → { access_token, id_token, refresh_token, expires_in }
5. 앱이 id_token 을 검증하고 세션을 만든다

Why go through a code once. If you give the token directly in the browser URL, the token is left in browser history, referrers, and logs. The authorization code is single-use and short-lived, and the real token is received server to server (back channel). That is why it is safe.

state and nonce have different roles. They are often confused.

What you must verify in an ID token

An ID token is a JWT. Three parts, 헤더.페이로드.서명 (header.payload.signature), are joined by dots, and each is base64url. Anyone can decode it. Signature verification is all of the safety.

Verification item What happens if you do not
Signature Anyone can tamper with the payload and log in as an administrator
iss (issuer) A token issued by the attacker's own IdP passes
aud (audience) A token issued for another app passes (token substitution)
exp (expiry) An expired token stays valid forever
nonce An old token is reused
alg alg: none or algorithm confusion attacks

The last row is the famous pitfall. If the JWT library trusts the algorithm the token itself declares when verifying, an attacker can change it to alg: none and pass without a signature. The verification code must pin the algorithm we expect.

Clock problems are also something you always meet in practice. For exp/iat verification, allow a clock skew tolerance of about 60–120 seconds. An outage where a single server with drifted NTP causes intermittent login failures takes a long time to diagnose.

SAML 2.0 — it is XML, and the signature is everything

SAML delivers one XML document (a Response containing an Assertion) by browser POST. The structure looks like this.

<samlp:Response Destination="https://app.example.com/saml/acs"
                InResponseTo="_req123" IssueInstant="...">
  <saml:Issuer>https://idp.example.com/</saml:Issuer>
  <samlp:Status><samlp:StatusCode Value="...:Success"/></samlp:Status>
  <saml:Assertion>
    <saml:Issuer>https://idp.example.com/</saml:Issuer>
    <ds:Signature>...</ds:Signature>          ← 서명
    <saml:Subject>
      <saml:NameID Format="...emailAddress">hong@labhub.co.kr</saml:NameID>
      <saml:SubjectConfirmation>...</saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="..." NotOnOrAfter="...">
      <saml:AudienceRestriction>
        <saml:Audience>https://app.example.com/sp</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AttributeStatement>
      <saml:Attribute Name="department"><saml:AttributeValue>개발팀</saml:AttributeValue></saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>

What the SP (our system) must verify corresponds to OIDC.

  1. Signature — does the Response or Assertion have a valid signature? And does the signed scope contain the data we trust?
  2. Issuer — is it the IdP we registered?
  3. Audience — was it issued for our SP?
  4. NotBefore / NotOnOrAfter — is it within its validity time? (allow clock skew)
  5. InResponseTo — is it a response to a request we sent?
  6. Destination — did it arrive at our ACS URL?
  7. Replay prevention — have we already used this Assertion ID?

The second sentence of item 1 is the core of the XML Signature Wrapping (XSW) attack. The attacker hides the original Assertion somewhere in the document and puts an unsigned fake Assertion in the position where the parser reads. Signature verification passes, but the data the app reads is fake. So you must check not only "is the signature valid" but "is the very node I am reading the signed node." Because this is hard, you should not implement SAML yourself but use a proven library.

SAML certificate expiry — a silent, total outage

In SAML, the SP registers the IdP's signing certificate in advance. When that certificate expires, every user's login fails at the same time. In a real large migration project, there was a case with a 47-minute total login outage caused by an expired signing certificate.

The defense is simple. Always put the SAML signing certificate on the certificate expiry watch list. Most watch lists monitor only server TLS certificates, so this gets missed.

OIDC vs SAML — practical choice

Item SAML 2.0 OIDC
Data format XML JSON (JWT)
Delivery path Mainly front channel (browser POST) Front + back channel
Mobile/SPA Inconvenient Suitable (PKCE)
Metadata exchange XML metadata file Discovery document (JSON)
Large Korean enterprises/public sector Still many New adoptions increasing
Implementation difficulty High (XML signatures) Medium

OIDC for something new, SAML if the counterpart already has SAML. And one interesting fact — if you register both an OIDC client and a SAML client in an IdP such as Keycloak, in the same SSO session one side gets a JWT and the other an XML Assertion. The IdP acts as a protocol conversion hub. In an SI environment where legacy and new coexist, this property becomes the core of the migration strategy.