private static Task OnValidatePrincipal( CookieValidatePrincipalContext context, OpenIdAuthOptions authOptions) { // Don't need to worry about about unauth request. if (context.Principal?.Identity == null || !context.Principal.Identity.IsAuthenticated) { return(Task.CompletedTask); } var absoluteExpiry = DateTime.Parse(context.Properties.Items[".Token.expires_at"]).ToUniversalTime(); // If the token is still not expired then don't need to reject the principal. if (absoluteExpiry > DateTime.UtcNow) { return(Task.CompletedTask); } // Token has expired, time to reject the Principal which will kick off the auth process again. var logger = context.HttpContext.RequestServices.GetRequiredService <ILogger <Startup> >(); logger.LogInformation( $"Token has expired @ {absoluteExpiry:O} compared to current date time of {DateTime.UtcNow:O}"); context.RejectPrincipal(); return(Task.CompletedTask); }
private static async Task <bool> RefreshAccessToken( CookieValidatePrincipalContext context, OpenIdAuthOptions authOptions) { // True = token refreshed. False = Token not refreshed and Principal will have to be rejected so the pipeline will kick off the login process. // Note that this function is not working as expected due to an issue with the context properties not updating correctly when setting the new // Expiry and Refresh token. if (context.Properties.Items.ContainsKey(".Token.refresh_token")) { var refreshToken = context.Properties.Items[".Token.refresh_token"]; // Reference token. var discoveryCache = context.HttpContext.RequestServices.GetRequiredService <IDiscoveryCache>(); var disco = await discoveryCache.GetAsync(); if (disco.IsError) { throw new Exception(disco.Error); } var client = context.HttpContext.RequestServices.GetRequiredService <IHttpClientFactory>() .CreateClient(); var refreshTokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = disco.TokenEndpoint, ClientId = authOptions.ClientId, ClientSecret = authOptions.ClientSecret, RefreshToken = refreshToken }); if (!refreshTokenResponse.IsError) { var id = await GetClaimsIdentity( context.Scheme.Name, refreshTokenResponse.AccessToken, context.HttpContext, authOptions); context.ReplacePrincipal(new ClaimsPrincipal(id)); context.Properties.Items[".Token.refresh_token"] = refreshTokenResponse.RefreshToken; context.Properties.Items[".Token.expires_at"] = DateTime.UtcNow.AddSeconds(refreshTokenResponse.ExpiresIn).ToString("O"); return(true); } } return(false); }
private static async Task SetupIdentityAndClaimsAndGa( TicketReceivedContext context, OpenIdAuthOptions authOptions) { var id = await GetClaimsIdentity( context.Scheme.Name, context.Properties.GetTokenValue("access_token"), context.HttpContext, authOptions, context.Principal.Claims?.ToArray()); await RegisterExternalAccount(context, id); try { await context.HttpContext.ConfirmAccountAsync( id, context.Properties.GetTokenValue("access_token")); } catch (Exception) { // Not logging here as it's already logged elsewhere. context.HandleResponse(); context.Response.Redirect("/error/authfailure"); return; } context.Principal = new ClaimsPrincipal(id); // The following is for GA. var subjectIdClaim = id.Claims.FirstOrDefault(x => x.Type == "sub"); if (subjectIdClaim != null) { var distributedCache = context.HttpContext.RequestServices.GetRequiredService <IDistributedCache>(); await distributedCache.SetAsync($"JUST-LOGGED-IN-{subjectIdClaim?.Value}", Encoding.UTF8.GetBytes("TRUE"), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5) }); } }
private static async Task <ClaimsIdentity> GetClaimsIdentity( string schemeName, string accessToken, HttpContext context, OpenIdAuthOptions authOptions, Claim[] additionalClaims = null) { var id = new ClaimsIdentity(schemeName); if (string.Equals(authOptions.TokenType, TokenTypes.Jwt, StringComparison.CurrentCultureIgnoreCase)) { // JWT token. var handler = new JwtSecurityTokenHandler(); var jwtToken = handler.ReadJwtToken(accessToken); id.AddClaims(jwtToken.Claims); } else { // Reference token. var discoveryCache = context.RequestServices.GetRequiredService <IDiscoveryCache>(); var disco = await discoveryCache.GetAsync(); if (disco.IsError) { throw new Exception(disco.Error); } var client = context.RequestServices.GetRequiredService <IHttpClientFactory>() .CreateClient(); var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest { Address = disco.IntrospectionEndpoint, ClientId = authOptions.ScopeId, ClientSecret = authOptions.ScopeSecret, Token = accessToken }); if (introspectionResponse.IsError) { throw new Exception(introspectionResponse.Error); } if (!introspectionResponse.IsActive) { throw new Exception("Obtained token is inactive."); } id.AddClaims(introspectionResponse.Claims); } if (additionalClaims != null && additionalClaims.Any()) { id.AddClaims(additionalClaims); } return(id); }
public static void AddOpenIdConnectAuth( this IServiceCollection services, OpenIdAuthOptions authOptions) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options => { // Surprisingly there are no DI for this! options.SessionStore = new RedisCacheTicketStore(new RedisCache(new RedisCacheOptions { Configuration = authOptions.RedisConfiguration })); options.Cookie.SameSite = SameSiteMode.None; // This is so we can manually check if the Token is expired so we can re-new it or logout the user. options.Events.OnValidatePrincipal = context => OnValidatePrincipal(context, authOptions); }) .AddOpenIdConnect(options => { options.Authority = authOptions.AuthorityEndpoint; options.ClientId = authOptions.ClientId; options.ClientSecret = authOptions.ClientSecret; options.ResponseType = "code id_token"; options.RequireHttpsMetadata = authOptions.RequireHttpsMetadata; // This sets the cookie expiry to match the ID Token if true. This is of no use so setting it to false. options.UseTokenLifetime = false; options.SaveTokens = true; foreach (var scope in authOptions.Scopes) { options.Scope.Add(scope); } // Form post is more secure. options.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost; options.RequireHttpsMetadata = options.RequireHttpsMetadata; if (options.Events == null) { options.Events = new OpenIdConnectEvents(); } options.Events.OnRemoteFailure = HandleRemoteFailure; options.Events.OnAuthenticationFailed = HandleAuthenticationFailure; options.Events.OnTicketReceived = context => SetupIdentityAndClaimsAndGa(context, authOptions); options.Events.OnRedirectToIdentityProvider = SetupAdditionalProperties; options.SecurityTokenValidator = new JwtSecurityTokenHandler { InboundClaimTypeMap = new Dictionary <string, string>() }; options.TokenValidationParameters.NameClaimType = ClaimTypes.Name; }); services.AddSingleton <IDiscoveryCache>(r => { var factory = r.GetRequiredService <IHttpClientFactory>(); return(new DiscoveryCache(authOptions.AuthorityEndpoint, () => factory.CreateClient(), new DiscoveryPolicy { RequireHttps = authOptions.RequireHttpsMetadata, AuthorityNameComparison = StringComparison.CurrentCultureIgnoreCase })); }); }