The document gives a security overview of popular SSO protocols. The overview
is focusing on end-to-end security and non-repudiation of SSO parties communication.
It goes through popular SSO (and not only) protocols and briefly explains which
and how tokens used, how they signed and encrypted.

The audience is prepared developer. The document expects reader to understand
how SSO works, particularly, what is HTTP redirect, Passive Requestor Profile or
what role certificates play in Web. The details are intentionally omitted.

HTTPS

RFCs dictate that all client-server communication is to be performed over HTTPS.
Thus we expect each involved party to provide valid and signed by certification
authority HTTPS certificate and all traffic between every two communicating points
is encrypted.

This is the only way to protect against man-in-the-middle, traffic sniffing and
session hijacking attacks. Thereby its must have for all SSO protocols.

SAML 2

SAML is one of well proven and adopted SSO protocols. Its the most reliable
SSO protocol. Corporate clients prefer it and it works well in environments where
communication security is critical. However, due to its design and XML format,
SAML message size is pretty big.

SAML SSO recognizes Identity Provider (IP or IDP) and Service Provider, which
trusts and delegates user authentication to IDP. Here is how trust is established:

  1. Service Provider (SP):

    • trusted IDP name and certificate
    • single sign on (SSO) URL
    • single log out (SLO) URL
  2. Identity Provider (IDP):

    • relying SP name and certificate
    • SSO consumer URL
    • SLO consumer URL

Whenever SP needs to authenticate user, it redirects it to SSO endpoint and
passes SAMLRequest wither in query string or form field (GET or POST method).

SAMLRequest is base64-encoded, deflated AuthnRequest XML object, which contains
information about SP and where to return SAMLResponse. It can also be signed by
SP using SHA-1 - in this case signature can be validated by IDP to ensure message
integrity. Signature is usually passed separately in query string argument.

IDP validates the request, logs user in, if needed, and sends back to SP
SAMLResponse as a form field. SAMLResponse is base64-encoded, deflated XML
Response object, which transfers general response information, time stamps, target
audience, IDPs certificate (so SP can validate signatures) and SAML token or SAML
assertion. SAML assertion contains identity and claims about authenticated user,
such as full name or email.

Either one or both SAMLResponse and assertion can be SHA-1-signed. The signatures
are embedded in SAMLResponse or/and assertion XML correspondingly. Assertion can
be AES-encrypted, in this case response will contain RSA-encrypted with SPs
certificate AES key.

Considering SAML request/response size, examples may easily take a few screens,
lets skip them and move ahead to the next section. If you are really curious, check
the References section at the end of the document.

One thing to mention before we leave SAML thou, is that generally SAML IDPs
expose meta-data XML file, which describes signing and encryption configuration,
supported set of claims and endpoints.

WS-Federation

WS-Federation is another well adopted and preferred by corporate clients way to
do SSO. Security configuration, message format and encryption it very similar to
SAML. In fact, WS-Federation utilizes SAML1.1 token (assertion) to pass claims
in response. Sources say, one of the reasons they are so similar, is that
WS-Federation appeared before SAML2 protocol.

WS-Federation identifies Secure Token Service (STS) and Relying Party (RP) -
this is very similar to SAMLs IDP and SP.

However, as oppose to SAML, WS-Federation RP sends open set of query string
parameters in initial authentication request:

https://some-host/?wa=wsignin1.0&wtrealm=https://some-host/&wctx=rm=0&id=passive&ru=%2f&wct=2014-10-08T14:46:07Z&wreply=https://some-host/

As we remember, SAML SP sends the whole XML AuthnRequest object, which can be
signed as well.

In response to successful login, STS sends RequestSecurityTokenResponse
XML object in form field (actually its wrapped in RequestSecurityTokenResponseCollection).
The object is pretty much equivalent to SAML Response, its also signed and
encrypted the same way (see above). And yes, its also holds SAML token, but the
schema is a bit different.

JSON Web Token

JSON Web Token or JWT is very different beast. As oppose to heavy and complex
SAML, JWT is lightweight and simplistic protocol, well suited for mobile or Web
clients. Here JSON stands for message payload format.

However, the loss of weight is really loss of security, the protocol doesn't
recommend transferring sensitive information in authentication response claims.
You surely know that HTTPS doesn't hide everything, cause JWT, being transferred
in request query string, may and will show up in browser history, referrer headers
or server logs.

As usual, JWT defines Identity Provider and Relying Party:

  1. Relying party (RP):

    • name
    • private secret (only known to RP and IDP)
    • IDP JWT endpoint
  2. Identity Provider (IDP):

    • name
    • private secret (only known to RP and IDP)

JWT authentication request holds JWT and return to URL, where user's browser
redirected on success. JWT itself contains unique ID generated by RP to prevent
reply attacks, issuer name (RP) and issued at time stamp. JWT consists of
three segments: header, payload and, typically, HS256 signature. All segments
are base64- and URL-encoded:

https://idp-host/jwt-login?returnUrl=https://rp-host/jwt-consumer/&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMzU1ZmVjMC04MmE3LTRmOWYtYTIzZS1kMDMwYTUxMDZiMDEiLCJpc3MiOiJKV1QuUmVseWluZ1BhcnR5In0.BVvfRB1jrINYr2enif5FJ42L61LABnHIkbSII_HqoKo

As you may know, the private secret plays important role when computing the
HMAC SHA256 signature, basically, its the key which is appended to the original
message before computing hash. The hash ensures message integrity and also
authenticates client.

IPD passes user through login screen and, in case of success, redirects browser
to RP with another JWT token, which contains user identity and claims:

http://rp-host/jwt-consumer/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmZjg1ZjFkZC1mOTJlLTQ1MDktOGIwMC1iMDhlZDEyYTY1ZDQiLCJpc3MiOiJKV1QuSWRlbnRpdHlQcm92aWRlciIsInVzciI6InNzbyJ9.iM0r3qfObVpZC9G7el6abdSUoY4SAWjQ4bxQbHzPmy0

After that RP decodes the message, validates signature and creates user session.

OAuth 2

OAuth is actually an authorization protocol which defines interoperation
among user, client, authorization server (AS) and resource server (RS), where
user's resource is hosted, in order to allow client access to the resource for a
limited time. But its interesting to mention it here, because there are plenty
signed and encrypted tokens going back and forth.

Lets look at the each party's security configuration.

  1. Client:

    • client ID
    • client secret
    • authorization endpoint
    • token endpoint
  2. Authorization server (AS):

    • client IDs and callbacks
    • users (can be delegated to 3rd-party IDP)
    • client authorizations store (authorizations have lifetime)
    • nonce store: authorization code grants (can be cache)
    • server certificate w/ private key, for access/refresh token signing
    • symmetric encryption key for authorization code encryption
    • symmetric encryption key for refresh token encryption
    • resource server certificate for access token encryption
  3. Resource server (RS):

    • server certificate w/ private key, for access token decryption
    • AS certificate for access token signature validation

Now lets look at the typical OAuth2 authorization flow and how the configuration
affects it. There are two main OAuth2 scenarios: authorization code grant and
implicit grant. Lets skip the second one since it's much simpler - it just skips
client app authentication step (no authorization code).

The first request client app makes for authorization is plain and open redirect
- there is nothing to sign or encrypt. Usually it contains client ID, return URL
and authorization scope in query string. if user logged in, AS responds with
client app authorization screen. If user authorizes the client, its redirected
back to client with authorization code grant:

code=8kc5!IAAAAKFPSJ5kn9bd49zM7CGMcpwAnxRBAU1KmdoyTVmomdIoAQEAAAHciBM_mpdHH0b7cheBTEayf3QXxXuH-exoV-7IUA7p40jXrs_Exb5XAiGtYerulagiI-gR8X8LjY24U5-Ce_YlV5sLQVZV5PrAvOxMg70LdaGop2WsH8yR8DKCS6ha4WcZZMZswrEA9UAKd0-gUMdHALGK7Iov0fpr-Cx4lYNWE2BMSEHSUTM6V8ST9G5Tp9rHlgvXHKo-z5Fz5aFMseLguxRFSH9XE03Z5ouSDchQ3yBzTHnHmucN10ireVrNZM1aM2kjZDnip93BwmJxKUpfj08tfUM6ep8lP2qiMOdZ-r6b528pJKjZMTBkoIXW6xjver6r_LgFuAd8Q0VJM2O1&state=YFmDc63bZ5Oi-9tdN6V4Yw

Here code is the authorization code grant the client wanted to get. Its base64-
encoded, generally Rijndael-encrypted and SHA-1 signed byte stream: client ID,
user ID, scope, Unix-time stamp and the nonce stored by AS to detect token reply
attacks. The authorization code can be deflated as well.

After obtaining the code, client passes it back to AS in exchange for access
and refresh tokens. At this step client also identifies itself with Authorization,
which holds client ID and secret. For example, in base64 may look like:

Authorization: Basic c2FtcGxlY29uc3VtZXI6c2FtcGxlc2VjcmV0

If challenge passed, AS responds with the tokens:

{
"access_token":"gAAAAKaTl278izpptpWRISOV9DDH5pO9vAi_lA0kCZQhXYRVS62a9RFV9dM2ua_nw_T3vDazdedDZUGIsJbWntwsI6zW6KDRQy4TYLOQsc-rU1qGA8-Tkmrx9hNEwjTg5ONYviV1Yoi0e3EWhef1sLbTFHkI0yFVShm0QJ7dRsJHNsHRJAEAAIAAAABdNEATd93uXuMR5fcpbzdD4O_oleYfhI_uDlkrd2YQ9o0-2I2PCg8UAvsCImM-JnWNh0gQ5wMDG6IWtAdMg_DvaRwVhWkVkh9zA1bDrar3RORW5ErKMaS1I1LgScurJn8R83udcsW82cc_cWGdO_h9vo4WYa7x00tTNyxmeTs81YvVQhrlc4k7GZy2_F0WJ1rTZhf-KAwcQEI5fFpKTnfBLRQlPsIR3sonDAchalUu3a1kETHS-F37rREIDQr5VYgbL_BPTj9b5qKRjnGUvYBdi7FDdfkZU1qawETmDWxsp_ujhqySipfx_Yb0VQjyTRZVEJOh3gnwJC9IWgJ7JC2DWMZPXvRexKGfGyXALD2PMIafkGjXK2rnqf9L8fjyroc",
"token_type":"bearer",
"expires_in":120,
"refresh_token":"UoTU!IAAAALZWgwQJQucH1X2c6PTJQQ7y6r19ZFN5JYGSNzdxpt2qsQAAAAEUurE6B0x6Epi29bGF_pVKI4gcqR2L_YUmCX-qZXrT5MJ6MnFPeTxQAifbFUZ1MPbR2N4HxVW3B_nUzzRKHcwndUCHLuy_HY_r2lH7XnnWCYBiRxu6809UCpuj0AWpZQGeVUyRU2w-Je7gYrlnvHiv38kyiFBxf1oFuXlm1SOwrZZ08Oa9Kqj4Tm4wCEnd5pMUJUCeA_AlxuWdqzeD-QObrzeXgFjhyIaVuim3B7B8Aw",
"scope":"http:\/\/tempuri.org\/IDataApi\/GetName"
}

Access token is base64-encoded, generally RSA-encrypted and SHA-1-signed client
ID, user ID, scope, Unix-time stamp, lifetime. Refresh token is pretty much the same,
but its Rijndael-encrypted and doesn't carry nonce. Remember, Rijndael is symmetric
encryption algorithm, so refresh token can only be decrypted by AS.

Each time client wants to request data from resource server (RS) on user's
behalf, it attaches Authorization header to request, e.g.:

Authorization: Bearer gAAAACgmhVfpHf9E32xUpBKj1QDw3ebZcYuF6DW8FDn5AZ76QBl8DoxewL_1iMQGhMdJ9HwtauxBxOOcZoTSKvFq39OkjEviMTD5X6ZO5ryAXTmF_YLnfXnF_48HkHKCD7PF4HKYKk8N1_bZo4H4tFPtdy7UKdepR6aBOCQ5IesTNKCm9AAAAIAAAAB5qkLSHPH4ICnONvTP0fAemGSInuqVTlpeLz-Qv5ohJdCREyJ0mSoWxE0tJwopUnRsJ3ebxgg8pSR1wKAoMx0-aUFjYs1fcHTi6Tz0Znssg3wk_6zbXZMpycjv7JtE3avTGZeZ8trkxgrBIqhgICbyPNaH8faSgzs9jgavLHiiysXrwXu2zp1YZzav7uss4JGYncsW0WfGnJiAgrjnkWKxhPs0XtP2Viujq6uuTF2O7SsWsYxmreT6WSerwJg2p9J-s50IXaSRPk7FDm-cNcwv6XJeFJqWKpzBu5PbpDB3rmMQYBEVM0JnJG01WxQS3xk

The header's value is nothing else, but the base64-encoded, RSA-encrypted access
token. In this case, RSA encryption uses resource server's public key, so the
tokens are only available to resource server.

On each request, RS tries to decrypt the access token, validate AS signature and
authorization scope. Only if all checks passed, RS responds with requested data.

OpenID

OpenID is an open standard which describes distributed user identity system.
Thou OpenID tries to scale SSO to size of Internet, it relies on technologies we
have mentioned above, like JWT to transfer claims. Also it adds another dimension
to the problem by introducing Identity Provider discovery.

You are welcome to read more about OpenID (see References), but I would like
to keep this section short just to not repeat myself.

Footnotes

RFCs don't dictate which algorithm to use to protect tokens integrity and
confidentiality, but the mentioned algorithms solve the problem well, they are
proven and have implementations in all popular platforms.

Note, that depending how tokens are transferred, either via query string or form
field, they can be URL- or HTML-encoded.

Add new comment