public async Task <IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); var logoutUrl = $"{Configuration.GetNonEmptyValue("Keycloak:Authority")}/protocol/openid-connect/logout"; var forwardedHost = HttpContext.Request.Headers.ContainsKey("X-Forwarded-Host") ? HttpContext.Request.Headers["X-Forwarded-Host"].ToString() : Request.Host.ToString(); var forwardedPort = HttpContext.Request.Headers["X-Forwarded-Port"]; //We are always sending X-Forwarded-Port, only time we aren't is when we are hitting the API directly. var baseUri = HttpContext.Request.Headers.ContainsKey("X-Forwarded-Host") ? $"{Configuration.GetNonEmptyValue("WebBaseHref")}logout" : "/api"; var applicationUrl = $"{XForwardedForHelper.BuildUrlString(forwardedHost, forwardedPort, baseUri)}"; var keycloakLogoutUrl = $"{logoutUrl}?post_logout_redirect_uri={applicationUrl}"; var siteMinderLogoutUrl = $"{Configuration.GetNonEmptyValue("SiteMinderLogoutUrl")}?returl={keycloakLogoutUrl}&retnow=1"; return(Redirect(siteMinderLogoutUrl)); }
public async Task <IActionResult> RequestCivilFileAccess([FromBody] RequestCivilFileAccess request) { if (string.IsNullOrEmpty(request.FileId) || string.IsNullOrEmpty(request.UserId)) { return(BadRequest()); } if (!User.IsServiceAccountUser()) { return(Forbid()); } var agencyId = string.IsNullOrEmpty(request.AgencyId) ? "" : AesGcmEncryption.Encrypt(request.AgencyId); var partId = string.IsNullOrEmpty(request.PartId) ? "" : AesGcmEncryption.Encrypt(request.PartId); var expiryMinutes = float.Parse(Configuration.GetNonEmptyValue("RequestCivilFileAccessMinutes")); await Db.RequestFileAccess.AddAsync(new RequestFileAccess { FileId = request.FileId, UserId = request.UserId, UserName = request.UserName, AgencyId = agencyId, PartId = partId, Requested = DateTimeOffset.Now, Expires = DateTimeOffset.Now.AddMinutes(expiryMinutes) }); await Db.SaveChangesAsync(); var forwardedHost = Request.Headers["X-Forwarded-Host"]; var forwardedPort = Request.Headers["X-Forwarded-Port"]; var baseUrl = Configuration.GetNonEmptyValue("WebBaseHref"); return(Ok(new { Url = $"{XForwardedForHelper.BuildUrlString(forwardedHost, forwardedPort, baseUrl)}civil-file/{request.FileId}?fromA2A=true" })); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var baseUrl = Configuration.GetNonEmptyValue("WebBaseHref"); app.Use((context, next) => { context.Request.EnableBuffering(); context.Request.Scheme = "https"; if (context.Request.Headers.ContainsKey("X-Forwarded-Host") && !env.IsDevelopment()) { context.Request.PathBase = new PathString(baseUrl.Remove(baseUrl.Length - 1)); } return(next()); }); app.UseForwardedHeaders(); app.UseCors(); app.UseSwagger(options => { options.RouteTemplate = "api/swagger/{documentname}/swagger.json"; options.PreSerializeFilters.Add((swaggerDoc, httpReq) => { if (!httpReq.Headers.ContainsKey("X-Forwarded-Host")) { return; } var forwardedHost = httpReq.Headers["X-Forwarded-Host"]; var forwardedPort = httpReq.Headers["X-Forwarded-Port"]; swaggerDoc.Servers = new List <OpenApiServer> { new OpenApiServer { Url = XForwardedForHelper.BuildUrlString(forwardedHost, forwardedPort, baseUrl) } }; }); }); app.UseSwaggerUI(options => { options.SwaggerEndpoint("swagger/v1/swagger.json", "SCV.API"); options.RoutePrefix = "api"; }); app.UseMiddleware(typeof(ErrorHandlingMiddleware)); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
public static IServiceCollection AddScvAuthentication(this IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration) { var baseUrl = configuration.GetNonEmptyValue("WebBaseHref"); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Cookie.Name = "SCV"; if (env.IsDevelopment()) { options.Cookie.Name += ".Development"; } options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Events = new CookieAuthenticationEvents { OnRedirectToAccessDenied = context => { context.Response.StatusCode = StatusCodes.Status403Forbidden; return(context.Response.CompleteAsync()); }, OnValidatePrincipal = async cookieCtx => { if (cookieCtx.Principal.Identity.AuthenticationType == SiteMinderAuthenticationHandler.SiteMinder) { return; } var accessTokenExpiration = DateTimeOffset.Parse(cookieCtx.Properties.GetTokenValue("expires_at")); var timeRemaining = accessTokenExpiration.Subtract(DateTimeOffset.UtcNow); var refreshThreshold = TimeSpan.Parse(configuration.GetNonEmptyValue("TokenRefreshThreshold")); if (timeRemaining > refreshThreshold) { return; } var refreshToken = cookieCtx.Properties.GetTokenValue("refresh_token"); var httpClientFactory = cookieCtx.HttpContext.RequestServices.GetRequiredService <IHttpClientFactory>(); var httpClient = httpClientFactory.CreateClient(nameof(CookieAuthenticationEvents)); var response = await httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = configuration.GetNonEmptyValue("Keycloak:Authority") + "/protocol/openid-connect/token", ClientId = configuration.GetNonEmptyValue("Keycloak:Client"), ClientSecret = configuration.GetNonEmptyValue("Keycloak:Secret"), RefreshToken = refreshToken }); if (response.IsError) { cookieCtx.RejectPrincipal(); await cookieCtx.HttpContext.SignOutAsync(CookieAuthenticationDefaults .AuthenticationScheme); } else { var expiresInSeconds = response.ExpiresIn; var updatedExpiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresInSeconds); cookieCtx.Properties.UpdateTokenValue("expires_at", updatedExpiresAt.ToString()); cookieCtx.Properties.UpdateTokenValue("refresh_token", response.RefreshToken); // Indicate to the cookie middleware that the cookie should be remade (since we have updated it) cookieCtx.ShouldRenew = true; } } }; } ) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = configuration.GetNonEmptyValue("Keycloak:Authority"); options.ClientId = configuration.GetNonEmptyValue("Keycloak:Client"); options.ClientSecret = configuration.GetNonEmptyValue("Keycloak:Secret"); options.RequireHttpsMetadata = true; options.GetClaimsFromUserInfoEndpoint = true; options.ResponseType = OpenIdConnectResponseType.Code; options.UsePkce = true; options.SaveTokens = true; options.CallbackPath = "/api/auth/signin-oidc"; options.Scope.Add("groups"); options.Scope.Add("vc_authn"); options.Events = new OpenIdConnectEvents { OnTicketReceived = context => { context.Properties.Items.Remove(".Token.id_token"); context.Properties.Items.Remove(".Token.access_token"); context.Properties.Items[".TokenNames"] = "refresh_token;token_type;expires_at"; return(Task.CompletedTask); }, #pragma warning disable 1998 OnTokenValidated = async context => #pragma warning restore 1998 { if (!(context.Principal.Identity is ClaimsIdentity identity)) { return; } var loggerFactory = context.HttpContext.RequestServices.GetRequiredService <ILoggerFactory>(); var logger = loggerFactory.CreateLogger("OnTokenValidated"); logger.LogInformation($"OpenIdConnect UserId - { context.Principal.UserId() } - logged in."); //Cleanup keycloak claims, that are unused. foreach (var claim in identity.Claims.WhereToList(c => !CustomClaimTypes.UsedKeycloakClaimTypes.Contains(c.Type))) { identity.RemoveClaim(claim); } var applicationCode = "SCV"; var partId = configuration.GetNonEmptyValue("Request:PartId"); var agencyId = configuration.GetNonEmptyValue("Request:AgencyIdentifierId"); var isSupremeUser = false; if (context.Principal.IsVcUser()) { var db = context.HttpContext.RequestServices.GetRequiredService <ScvDbContext>(); var userId = context.Principal.UserId(); var now = DateTimeOffset.Now; var fileAccess = await db.RequestFileAccess .Where(r => r.UserId == userId && r.Expires > now) .OrderByDescending(x => x.Id) .FirstOrDefaultAsync(); if (fileAccess != null && !string.IsNullOrEmpty(fileAccess.PartId) && !string.IsNullOrEmpty(fileAccess.AgencyId)) { logger.LogInformation($"UserId - { context.Principal.UserId() } - Using credentials passed in from A2A."); var aesGcmEncryption = context.HttpContext.RequestServices.GetRequiredService <AesGcmEncryption>(); partId = aesGcmEncryption.Decrypt(fileAccess.PartId); agencyId = aesGcmEncryption.Decrypt(fileAccess.AgencyId); applicationCode = "A2A"; } } else if (context.Principal.IsIdirUser() && context.Principal.Groups().Contains("court-viewer-supreme")) { isSupremeUser = true; } var claims = new List <Claim>(); claims.AddRange(new[] { new Claim(CustomClaimTypes.ApplicationCode, applicationCode), new Claim(CustomClaimTypes.JcParticipantId, partId), new Claim(CustomClaimTypes.JcAgencyCode, agencyId), new Claim(CustomClaimTypes.IsSupremeUser, isSupremeUser.ToString()) }); identity.AddClaims(claims); }, OnRedirectToIdentityProvider = context => { if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication) { if (!context.Request.Path.StartsWithSegments("/api/auth/login")) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; context.HandleResponse(); return(Task.CompletedTask); } } context.ProtocolMessage.SetParameter("kc_idp_hint", context.Request.Query["redirectUri"].ToString().Contains("fromA2A=true") ? configuration.GetNonEmptyValue("Keycloak:VcIdpHint") : "idir"); context.ProtocolMessage.SetParameter("pres_req_conf_id", configuration.GetNonEmptyValue("Keycloak:PresReqConfId")); if (context.HttpContext.Request.Headers["X-Forwarded-Host"].Count > 0) { var forwardedHost = context.HttpContext.Request.Headers["X-Forwarded-Host"]; var forwardedPort = context.HttpContext.Request.Headers["X-Forwarded-Port"]; context.ProtocolMessage.RedirectUri = $"{XForwardedForHelper.BuildUrlString(forwardedHost, forwardedPort, baseUrl)}{options.CallbackPath}"; } return(Task.CompletedTask); } }; })