OIDC is supposed to make things easier, so I thought it would be a good exercise to write a web application that uses OIDC to authenticate users – but without using any OIDC specific libraries.
I chose to use the implicit flow with the form post response mode – which is very similar to the WS-Federation or SAML2p POST profiles. Let’s see how much code it takes.
Initiating the Request
The first task is to create the authentication request for the OIDC authorize endpoint. This involves constructing a query string that contains the client id, scope, redirect URL, response type and response mode. In addition you need to create two random numbers for state and nonce. State is used to correlate the authentication response, nonce is used to correlate the identity token coming back. Both values need to be stored temporarily (I use a cookie for that).
public ActionResult SignIn()
var state = Guid.NewGuid().ToString("N");
var nonce = Guid.NewGuid().ToString("N");
var url = Constants.AuthorizeEndpoint +
"&scope=openid email" +
"&state=" + state +
"&nonce=" + nonce;
Handling the Callback
When the OIDC provider is done with its work, it sends back an identity token as part of a form POST back to our application. We can fetch the token (and the state) from the forms collection, validate the token (more on that shortly) and if successful sign the user in locally using an authentication cookie.
public async Task<ActionResult> SignInCallback()
var token = Request.Form["id_token"];
var state = Request.Form["state"];
var claims = await ValidateIdentityTokenAsync(token, state);
var id = new ClaimsIdentity(claims, "Cookies");
Validating the Token
There is an exact specification on how to validate identity tokens and the authentication response (see here) – this involves:
- Validate the JWT
- the audience must match the client id
- the issuer must match the expected issuer name
- the signing key must match the expected issuer signing key
- the token must be in its validity time period
- The state parameter on the response must match the state value that we sent to the provider on the initial request
- The nonce claim inside the identity token must match the nonce that we sent to the provider on the initial request
Most of the work is done by the JWT token library from Microsoft (see here for alternatives on different platforms).
private async Task<IEnumerable<Claim>> ValidateIdentityTokenAsync(
string token, string state)
var result = await Request
if (result == null)
throw new InvalidOperationException("No temp cookie");
if (state != result.Identity.FindFirst("state").Value)
throw new InvalidOperationException("invalid state");
var parameters = new TokenValidationParameters
AllowedAudience = "implicitclient",
ValidIssuer = "https://idsrv3.com",
SigningToken = new X509SecurityToken(
var handler = new JwtSecurityTokenHandler();
var id = handler.ValidateToken(token, parameters);
if (id.FindFirst("nonce").Value !=
throw new InvalidOperationException("Invalid nonce");
Done. This wasn’t too bad! The full sample using IdentityServer v3 as the provider can be found here.
In the next post I will show how you can simplify this by using the OpenID discovery document and OWIN middleware. Stay tuned.