void OnEndRequest(object sender, EventArgs e)
        {
            var context = HttpContext.Current;

            if (context.Response.StatusCode == 401 &&
                !context.User.Identity.IsAuthenticated &&
                !context.Response.SuppressFormsAuthenticationRedirect)
            {
                var config = OidcClientConfigurationSection.Instance;

                var authorizeUrl = config.Endpoints.Authorize;
                var clientId = config.ClientId;
                var scopes = "openid " + config.Scope;
                var state = Guid.NewGuid().ToString("N");
                var returnUrl = context.Request.RawUrl;
                var redirectUri = context.Request.GetApplicationUrl() + "oidccallback";

                var authorizeUri = OidcClient.GetAuthorizeUrl(
                    new Uri(authorizeUrl),
                    new Uri(redirectUri),
                    clientId,
                    scopes,
                    state);

                var cookie = new ProtectedCookie(ProtectionMode.MachineKey);
                cookie.Write("oidcstate", state + "_" + returnUrl, DateTime.UtcNow.AddHours(1));

                context.Response.Redirect(authorizeUri.AbsoluteUri);
            }
        }
        async Task AuthenticateAsync(HttpContext context)
        {
            var config = OidcClientConfigurationSection.Instance;

            var appRelativeCallbackUrl = config.AppRelativeCallbackUrl;
            if (context.Request.AppRelativeCurrentExecutionFilePath.Equals(appRelativeCallbackUrl, StringComparison.OrdinalIgnoreCase))
            {
                var authorizeErrorUrl = config.AuthorizeErrorRedirectUrl;
                var tokenUrl = config.Endpoints.Token;
                var userInfoUrl = config.Endpoints.UserInfo;
                var clientId = config.ClientId;
                var clientSecret = config.ClientSecret;
                var issuerName = config.IssuerName;
                var signingcert = X509.LocalMachine.TrustedPeople.SubjectDistinguishedName.Find(
                    config.SigningCertificate, false).First();
                var callUserInfoEndpoint = config.CallUserInfoEndpoint;

                // parse OIDC authorize response
                var response = OidcClient.HandleAuthorizeResponse(context.Request.QueryString);

                // event? callback recieved, pass response (allow cancel and need new url?)
                var authorizeResponseEventArgs = new AuthorizeResponseEventArgs { Response = response };
                OnAuthorizeResponse(authorizeResponseEventArgs);
                if (authorizeResponseEventArgs.Cancel)
                {
                    if (String.IsNullOrWhiteSpace(authorizeResponseEventArgs.RedirectUrl))
                    {
                        throw new ArgumentNullException("RedirectUrl");
                    }
                    context.Response.Redirect(authorizeResponseEventArgs.RedirectUrl);
                    return;
                }

                if (response.IsError)
                {
                    if (!String.IsNullOrWhiteSpace(authorizeErrorUrl))
                    {
                        if (!authorizeErrorUrl.Contains("?"))
                        {
                            authorizeErrorUrl += "?";
                        }
                        if (!authorizeErrorUrl.EndsWith("?"))
                        {
                            authorizeErrorUrl += "&";
                        }
                        authorizeErrorUrl += "error=" + response.Error;
                        context.Response.Redirect(authorizeErrorUrl);
                        return;
                    }

                    throw new InvalidOperationException("OpenID Connect Callback Error: " + response.Error + ". Handle the AuthorizeResponse event to handle authorization errors.");
                }

                try
                {
                    // read and parse state cookie
                    var cookie = new ProtectedCookie(ProtectionMode.MachineKey);
                    var storedState = cookie.Read("oidcstate");
                    ProtectedCookie.Delete("oidcstate");

                    var separator = storedState.IndexOf('_');
                    if (separator == -1)
                    {
                        throw new InvalidOperationException("state invalid.");
                    }

                    var state = storedState.Substring(0, separator);
                    var returnUrl = storedState.Substring(separator + 1);
                    
                    if (appRelativeCallbackUrl.StartsWith("~/"))
                    {
                        appRelativeCallbackUrl = appRelativeCallbackUrl.Substring(2);
                    }
                    var redirectUri = context.Request.GetApplicationUrl() + appRelativeCallbackUrl;

                    // validate state
                    if (response.State != state)
                    {
                        throw new InvalidOperationException("state invalid.");
                    }

                    // call token endpoint and retrieve id and access token (and maybe a refresh token)
                    var tokenResponse = await OidcClient.CallTokenEndpointAsync(
                        new Uri(tokenUrl),
                        new Uri(redirectUri),
                        response.Code,
                        clientId,
                        clientSecret);

                    // event -- token obtained event
                    var tokenResponseEventArgs = new TokenResponseEventArgs { Response = tokenResponse };
                    OnTokenResponse(tokenResponseEventArgs);
                    if (tokenResponseEventArgs.Cancel)
                    {
                        if (String.IsNullOrWhiteSpace(tokenResponseEventArgs.RedirectUrl))
                        {
                            throw new ArgumentNullException("RedirectUrl");
                        } 
                        
                        context.Response.Redirect(tokenResponseEventArgs.RedirectUrl);
                        return;
                    }

                    // validate identity token
                    var identityClaims = OidcClient.ValidateIdentityToken(
                        tokenResponse.IdentityToken,
                        issuerName,
                        clientId,
                        signingcert);
                    
                    // event -- identity token validated w/ claims
                    var identityTokenValidatedEventArgs = new IdentityTokenValidatedEventArgs { Claims = identityClaims };
                    OnIdentityTokenValidated(identityTokenValidatedEventArgs);
                    if (identityTokenValidatedEventArgs.Cancel)
                    {
                        if (String.IsNullOrWhiteSpace(identityTokenValidatedEventArgs.RedirectUrl))
                        {
                            throw new ArgumentNullException("RedirectUrl");
                        }

                        context.Response.Redirect(identityTokenValidatedEventArgs.RedirectUrl);
                        return;
                    }

                    var claims = identityTokenValidatedEventArgs.Claims.ToList();

                    if (callUserInfoEndpoint)
                    {
                        // retrieve user info data
                        var userInfoClaims = await OidcClient.GetUserInfoClaimsAsync(
                            new Uri(userInfoUrl),
                            tokenResponse.AccessToken);

                        // event -- profile loaded w/ claims
                        var userInfoClaimsReceivedEventArgs = new UserInfoClaimsReceivedEventArgs { Claims = userInfoClaims };
                        OnUserInfoClaimsReceived(userInfoClaimsReceivedEventArgs);
                        if (userInfoClaimsReceivedEventArgs.Cancel)
                        {
                            if (String.IsNullOrWhiteSpace(userInfoClaimsReceivedEventArgs.RedirectUrl))
                            {
                                throw new ArgumentNullException("RedirectUrl");
                            }

                            context.Response.Redirect(userInfoClaimsReceivedEventArgs.RedirectUrl);
                            return;
                        }

                        claims = userInfoClaimsReceivedEventArgs.Claims.ToList();
                    }

                    // create identity
                    claims.Add(new Claim("at", tokenResponse.AccessToken));
                    var id = new ClaimsIdentity(claims, "oidc");

                    if (!string.IsNullOrWhiteSpace(tokenResponse.RefreshToken))
                    {
                        id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                    }

                    // create principal
                    var principal = new ClaimsPrincipal(id);
                    var transformedPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager.Authenticate(string.Empty, principal);

                    // establish session
                    var sessionToken = new SessionSecurityToken(transformedPrincipal);

                    // event? raise session security token created
                    var sessionTokenCreatedEventArgs = new SessionTokenCreatedEventArgs { Token = sessionToken };
                    OnSessionSecurityTokenCreated(sessionTokenCreatedEventArgs);
                        
                    FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);

                    // event? signed in -- pass in return URL and allow them to change
                    OnSignedIn();

                    // redirect local to return url
                    if (!string.IsNullOrWhiteSpace(returnUrl))
                    {
                        context.Response.Redirect(returnUrl);
                    }
                    else
                    {
                        context.Response.Redirect("~/");
                    }
                }
                catch (Exception ex)
                {
                    // event? general error/fail
                    var errorEventArgs = new ErrorEventArgs() { Exception = ex };
                    OnError(errorEventArgs);
                    if (!errorEventArgs.ExceptionHandled)
                    {
                        throw; 
                    }
                }
            }
        }
        void OnAuthenticateRequestRequest(object sender, EventArgs e)
        {
            var context = HttpContext.Current;

            if (context.Request.AppRelativeCurrentExecutionFilePath.Equals("~/oidccallback", StringComparison.OrdinalIgnoreCase))
            {
                var config = OidcClientConfigurationSection.Instance;

                var tokenUrl = config.Endpoints.Token;
                var userInfoUrl = config.Endpoints.UserInfo;
                var clientId = config.ClientId;
                var clientSecret = config.ClientSecret;
                var issuerName = config.IssuerName;
                var signingcert = X509.LocalMachine.TrustedPeople.SubjectDistinguishedName.Find(
                    config.SigningCertificate).First();

                // parse OIDC authorize response
                var response = OidcClient.HandleAuthorizeResponse(context.Request.QueryString);

                if (response.IsError)
                {
                    throw new InvalidOperationException(response.Error);
                }

                // read and parse state cookie
                var cookie = new ProtectedCookie(ProtectionMode.MachineKey);
                var storedState = cookie.Read("oidcstate");

                var separator = storedState.IndexOf('_');
                if (separator == -1)
                {
                    throw new InvalidOperationException("state invalid.");
                }

                var state = storedState.Substring(0, separator);
                var returnUrl = storedState.Substring(separator + 1);
                var redirectUri = context.Request.GetApplicationUrl() + "oidccallback";

                // validate state
                if (response.State != state)
                {
                    throw new InvalidOperationException("state invalid.");
                }

                // call token endpoint and retrieve id and access token (and maybe a refresh token)
                var tokenResponse = OidcClient.CallTokenEndpoint(
                    new Uri(tokenUrl),
                    new Uri(redirectUri),
                    response.Code,
                    clientId,
                    clientSecret);

                // validate identity token
                var identityClaims = OidcClient.ValidateIdentityToken(
                    tokenResponse.IdentityToken,
                    issuerName,
                    clientId,
                    signingcert);

                // retrieve user info data
                var userInfoClaims = OidcClient.GetUserInfoClaims(
                    new Uri(userInfoUrl),
                    tokenResponse.AccessToken);

                // create identity
                var id = new ClaimsIdentity(userInfoClaims, "oidc");

                if (!string.IsNullOrWhiteSpace(tokenResponse.RefreshToken))
                {
                    id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                }

                // create principal
                var principal = new ClaimsPrincipal(id);
                var transformedPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager.Authenticate(string.Empty, principal);

                // establish session
                var sessionToken = new SessionSecurityToken(transformedPrincipal);
                FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);

                // redirect local to return url
                if (!string.IsNullOrWhiteSpace(returnUrl))
                {
                    context.Response.Redirect(returnUrl);
                }
                else
                {
                    context.Response.Redirect("~/");
                }
            }
        }