public override async Task Authorize(HttpContext context) { // get the necessary variables string authority = CasConfig.AzureAuthority; string clientId = WebUtility.UrlEncode(CasConfig.AzureClientId); string redirect = WebUtility.UrlEncode(CasConfig.RedirectUri(context.Request)); string domainHint = WebUtility.UrlEncode(CasConfig.AzureDomainHint); // get the scope var basicScope = "openid profile email"; var scope = await AppendScope(basicScope, "microsoft.com"); // define the response type string responseType = (scope == basicScope) ? WebUtility.UrlEncode("id_token") : WebUtility.UrlEncode("id_token code"); // write flow cookie var flow = WriteFlowCookie(context); // redirect to url scope = WebUtility.UrlEncode(scope); string url = $"{authority}/oauth2/v2.0/authorize?response_type={responseType}&client_id={clientId}&redirect_uri={redirect}&scope={scope}&response_mode=form_post&state={flow.state}&nonce={flow.nonce}"; if (!string.IsNullOrEmpty(domainHint)) { url += $"&domain_hint={domainHint}"; } context.Response.Redirect(url); await context.Response.CompleteAsync(); }
private async Task <Tokens> GetAccessTokenFromAuthCode(HttpContext context, string code, string scope) { // get the client secret var secret = await Config.AzureClientSecret(); // get the response using (var request = new HttpRequestMessage() { RequestUri = new Uri($"{CasConfig.AzureAuthority}/oauth2/v2.0/token"), Method = HttpMethod.Post }) { using (request.Content = new FormUrlEncodedContent(new[] { new KeyValuePair <string, string>("client_id", CasConfig.AzureClientId), new KeyValuePair <string, string>("client_secret", secret), new KeyValuePair <string, string>("scope", scope), new KeyValuePair <string, string>("code", code), new KeyValuePair <string, string>("redirect_uri", CasConfig.RedirectUri(context.Request)), new KeyValuePair <string, string>("grant_type", "authorization_code") })) { using (var response = await HttpClient.SendAsync(request)) { var raw = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"GetAccessTokenFromAuthCode: HTTP {(int)response.StatusCode} - {raw}"); } var tokens = JsonConvert.DeserializeObject <Tokens>(raw); return(tokens); } } }; }
protected async Task WriteTokenCookies(HttpContext context, List <Claim> claims) { var domain = CasConfig.BaseDomain(context.Request); // write the XSRF-TOKEN cookie (if it will be verified) if (CasConfig.VerifyXsrfInHeader || CasConfig.VerifyXsrfInCookie) { string xsrf = this.GenerateSafeRandomString(16); string signed = xsrf; if (!CasConfig.RequireHttpOnlyOnUserCookie) { // if the source claim is going to be in a cookie that is readable by JavaScript the XSRF must be signed signed = await TokenIssuer.IssueXsrfToken(xsrf); } context.Response.Cookies.Append("XSRF-TOKEN", signed, new CookieOptions() { HttpOnly = CasConfig.RequireHttpOnlyOnXsrfCookie, Secure = CasConfig.RequireSecureForCookies, Domain = domain, SameSite = CasConfig.SameSite, Path = "/" }); Logger.LogInformation($"wrote XSRF-TOKEN cookie on domain \"{domain}\"."); claims.Add(new Claim("xsrf", xsrf)); } // write the user cookie string jwt = await TokenIssuer.IssueToken(claims); Console.WriteLine("jwt....................................jwt"); Console.WriteLine(jwt); Console.WriteLine("jwt....................................jwt"); var userCookie = CasConfig.UserCookieName; context.Response.Cookies.Append(userCookie, jwt, new CookieOptions() { HttpOnly = CasConfig.RequireHttpOnlyOnUserCookie, Secure = CasConfig.RequireSecureForCookies, Domain = domain, SameSite = CasConfig.SameSite, Path = "/" }); Logger.LogInformation($"wrote session cookie as \"{userCookie}\" on domain \"{domain}\"."); // revoke the authflow cookie context.Response.Cookies.Delete("authflow"); }
public override async Task Authorize(HttpContext context) { // get the necessary variables string authority = "https://accounts.google.com"; string clientId = WebUtility.UrlEncode(CasConfig.GoogleClientId); string redirect = WebUtility.UrlEncode( // NOTE: google sends the values on a filter, so we need to extract via javascript CasConfig.RedirectUri(context.Request).Replace("/cas/token", "/cas/extract") ); string domainHint = WebUtility.UrlEncode(CasConfig.GoogleDomainHint); // get the scope var basicScope = "openid profile email"; var scope = await AppendScope(basicScope, "google.com"); // define the response type string responseType = (scope == basicScope) ? WebUtility.UrlEncode("id_token") : WebUtility.UrlEncode("id_token code"); // NOTE: I have not tested authcode flow with google yet if (scope != basicScope) { throw new System.NotSupportedException(); } // write flow cookie var flow = WriteFlowCookie(context); // redirect to url scope = WebUtility.UrlEncode(scope); string url = $"{authority}/o/oauth2/v2/auth?response_type={responseType}&client_id={clientId}&redirect_uri={redirect}&scope={scope}&state={flow.state}&nonce={flow.nonce}"; if (!string.IsNullOrEmpty(domainHint)) { url += $"&hd={domainHint}"; } context.Response.Redirect(url); await context.Response.CompleteAsync(); }
public static async Task AddCasServerAuthAsync(this IServiceCollection services) { // add the logger services.AddSingleLineConsoleLogger(); // add HttpClients services.AddHttpClient("netbricks") .ConfigurePrimaryHttpMessageHandler(() => new CasProxyHandler()); services.AddHttpClient("cas") .ConfigurePrimaryHttpMessageHandler(() => new CasProxyHandler()); // add the access token fetcher services.AddAccessTokenFetcher(); // add the configuration service services.TryAddSingleton <IConfig, CasConfig>(); // load the configuration and log it using (var provider = services.BuildServiceProvider()) { var logger = provider.GetService <ILogger <CasServerAuthServicesConfiguration> >(); var authCodeReceiver = provider.GetService <ICasAuthCodeReceiver>(); // get the config var config = provider.GetService <IConfig>() as CasConfig; if (config == null) { throw new Exception("AddCasClientAuth: CasConfig could not be found in the IServiceCollection."); } // determine the authentication type var authType = config.AuthType(); if (authType == AuthTypes.Service) { logger.LogInformation("authentication: application ClientId and ClientSecret (service principal)."); } if (authType == AuthTypes.Token) { logger.LogInformation("authentication: managed identity with failback to az cli."); } // load the configuration logger.LogInformation("Loading configuration..."); await config.Apply(); // confirm and log the configuration config.Optional("AUTH_TYPE", authType.ToString()); config.Optional("AUTH_TYPE_CONFIG", config.AuthType("CONFIG").ToString()); config.Optional("AUTH_TYPE_VAULT", config.AuthType("VAULT").ToString()); config.Optional("AUTH_TYPE_GRAPH", config.AuthType("GRAPH").ToString()); config.Require("AZURE_TENANT_ID", CasConfig.AzureTenantId); config.Require("AZURE_CLIENT_ID", CasConfig.AzureClientId); if (authType == AuthTypes.Service || authCodeReceiver != null) { config.Require("AZURE_CLIENT_SECRET", await config.AzureClientSecret(), hideValue: true); } else { config.Optional("AZURE_CLIENT_SECRET", await config.AzureClientSecret(), hideValue: true); } config.Optional("APPCONFIG", CasConfig.AppConfig, hideIfEmpty: true); config.Optional("CONFIG_KEYS", CasConfig.ConfigKeys, hideIfEmpty: true); config.Optional("PROXY", CasConfig.Proxy, hideIfEmpty: true); config.Optional("DEFAULT_HOST_URL", CasConfig.DefaultHostUrl, hideIfEmpty: true); config.Optional("SERVER_HOST_URL", CasConfig.ServerHostUrl, hideIfEmpty: true); config.Optional("CLIENT_HOST_URL", CasConfig.ClientHostUrl, hideIfEmpty: true); config.Optional("WEB_HOST_URL", CasConfig.WebHostUrl, hideIfEmpty: true); config.Optional("USE_INSECURE_DEFAULTS", CasConfig.UseInsecureDefaults, hideValue: false); config.Optional("IS_HTTPS", CasConfig.IsHttps, hideValue: false); config.Require("AZURE_AUTHORITY", CasConfig.AzureAuthority); config.Optional("GOOGLE_CLIENT_ID", CasConfig.GoogleClientId); config.Require("REDIRECT_URI", CasConfig.RedirectUri()); config.Require("ISSUER", CasConfig.Issuer); config.Require("AUDIENCE", CasConfig.Audience); config.Require("ALLOWED_ORIGINS", CasConfig.AllowedOrigins); config.Require("BASE_DOMAIN", CasConfig.BaseDomain()); config.Require("PUBLIC_KEYS_URL", CasConfig.PublicKeysUrl); config.Require("PRIVATE_KEY", await config.PrivateKey(), hideValue: true); config.Require("PRIVATE_KEY_PASSWORD", await config.PrivateKeyPassword(), hideValue: true); config.Require("PUBLIC_CERT_0", await config.PublicCert(0), hideValue: true); config.Optional("PUBLIC_CERT_1", await config.PublicCert(1), hideValue: true, hideIfEmpty: true); config.Optional("PUBLIC_CERT_2", await config.PublicCert(2), hideValue: true, hideIfEmpty: true); config.Optional("PUBLIC_CERT_3", await config.PublicCert(3), hideValue: true, hideIfEmpty: true); config.Optional("DEFAULT_REDIRECT_URL", CasConfig.DefaultRedirectUrl); config.Optional("AZURE_APPLICATION_ID", CasConfig.AzureApplicationIds, hideIfEmpty: true); config.Optional("REQUIRE_SECURE_FOR_COOKIES", CasConfig.RequireSecureForCookies, hideValue: false); config.Optional("REQUIRE_USER_ENABLED_ON_REISSUE", CasConfig.RequireUserEnabledOnReissue, hideValue: false); config.Optional("REQUIRE_HTTPONLY_ON_USER_COOKIE", CasConfig.RequireHttpOnlyOnUserCookie, hideValue: false); config.Optional("REQUIRE_HTTPONLY_ON_XSRF_COOKIE", CasConfig.RequireHttpOnlyOnXsrfCookie, hideValue: false); config.Optional("VERIFY_XSRF_IN_HEADER", CasConfig.VerifyXsrfInHeader, hideValue: false); config.Optional("VERIFY_XSRF_IN_COOKIE", CasConfig.VerifyXsrfInCookie, hideValue: false); config.Optional("SAME_SITE", CasConfig.SameSite.ToString()); config.Optional("USER_COOKIE_NAME", CasConfig.UserCookieName); config.Optional("ROLE_FOR_ADMIN", CasConfig.RoleForAdmin); config.Optional("ROLE_FOR_SERVICE", CasConfig.RoleForService); config.Optional("AZURE_DOMAIN_HINT", CasConfig.AzureDomainHint, hideIfEmpty: true); config.Optional("GOOGLE_DOMAIN_HINT", CasConfig.GoogleDomainHint, hideIfEmpty: true); config.Optional("GOOGLE_EMAIL_MUST_BE_VERIFIED", CasConfig.GoogleEmailMustBeVerified, hideValue: false); config.Optional("JWT_DURATION", CasConfig.JwtDuration.ToString()); config.Optional("JWT_SERVICE_DURATION", CasConfig.JwtServiceDuration.ToString()); config.Optional("JWT_MAX_DURATION", CasConfig.JwtMaxDuration.ToString()); config.Optional("COMMAND_PASSWORD", await config.CommandPassword(), hideValue: true); } // add the issuer service services.AddSingleton <CasTokenIssuer, CasTokenIssuer>(); // add the IDPs services.AddSingleton <ICasIdp, CasAzureAd>(); if (!string.IsNullOrEmpty(CasConfig.GoogleClientId)) { services.AddSingleton <ICasIdp, CasGoogleId>(); } // setup CORS policy if (CasConfig.AllowedOrigins.Length > 0) { services.AddCors(options => { options.AddPolicy("cas-server", builder => { builder.WithOrigins(CasConfig.AllowedOrigins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); } }
protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { bool isTokenFromHeader = false; bool isTokenFromCookie = false; try { Logger.LogDebug("CasAuthentication: started authentication check..."); // drop all internal identity headers so they aren't propogated Request.Headers.Remove("X-IDENTITY"); Request.Headers.Remove("X-EMAIL"); Request.Headers.Remove("X-NAME"); Request.Headers.Remove("X-ROLES"); // check first for header string token = string.Empty; var header = Request.Headers["Authorization"]; if (header.Count() > 0) { Logger.LogDebug($"CasAuthentication: checking header named \"Authorization\" for token..."); token = header.First().Replace("Bearer ", ""); isTokenFromHeader = true; } // look next at the cookie if (CasConfig.VerifyTokenInCookie && string.IsNullOrEmpty(token)) { Logger.LogDebug($"CasAuthentication: checking cookie named \"{CasConfig.UserCookieName}\" for token..."); token = Request.Cookies[CasConfig.UserCookieName]; isTokenFromCookie = true; } // shortcut if there is no token if (string.IsNullOrEmpty(token)) { Logger.LogDebug("CasAuthentication: no token was found."); return(AuthenticateResult.NoResult()); } // attempt reissue is appropriate if ( !string.IsNullOrEmpty(CasConfig.ReissueUrl) && // reissue is allowed (there is a URL) CasTokenValidator.IsTokenExpired(token) // the token is expired ) { // attempt to reissue Logger.LogDebug("CasAuthentication: attempted to reissue an expired token..."); var httpClientFactory = Request.HttpContext.RequestServices.GetService(typeof(IHttpClientFactory)) as IHttpClientFactory; var httpClient = httpClientFactory.CreateClient("cas"); token = await CasTokenValidator.ReissueToken(httpClient, token); Logger.LogDebug("CasAuthentication: reissued token successfully"); // rewrite the cookie if (isTokenFromCookie) { Response.Cookies.Append(CasConfig.UserCookieName, token, new CookieOptions() { HttpOnly = CasConfig.RequireHttpOnlyOnUserCookie, Secure = CasConfig.RequireSecureForCookies, Domain = CasConfig.BaseDomain(Request), SameSite = CasConfig.SameSite, Path = "/" }); } } // validate the token var jwt = await this.CasTokenValidator.ValidateToken(token); // if the token was in the header and that wasn't allowed, it had better be a service account if (isTokenFromHeader && !CasConfig.VerifyTokenInHeader && !jwt.Payload.Claims.IsService() ) { throw new Exception("only service account types are allowed in the header"); } // propogate the claims (this overload uses uri-names and dedupes) var claims = new List <Claim>(); foreach (var claim in jwt.Payload.Claims) { claims.AddLong(claim.Type, claim.Value); } // build the identity, principal, and ticket var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); return(AuthenticateResult.Success(ticket)); } catch (Exception e) { Logger.LogWarning(e, "CasAuthentication: exception..."); if (isTokenFromCookie) { Response.Cookies.Delete(CasConfig.UserCookieName); // revoke the cookie } return(AuthenticateResult.Fail(e)); } }
public static async Task AddCasClientAuthAsync(this IServiceCollection services) { // add the logger services.AddSingleLineConsoleLogger(); // add the HttpContext services.AddHttpContextAccessor(); // add HttpClients services.AddHttpClient("netbricks") .ConfigurePrimaryHttpMessageHandler(() => new CasProxyHandler()); services.AddHttpClient("cas") .ConfigurePrimaryHttpMessageHandler(() => new CasProxyHandler()); // add the access token fetcher services.AddAccessTokenFetcher(); // add the configuration service services.TryAddSingleton <IConfig, CasConfig>(); // load the configuration and log it using (var provider = services.BuildServiceProvider()) { var logger = provider.GetService <ILogger <CasClientAuthServicesConfiguration> >(); // get the config var config = provider.GetService <IConfig>() as CasConfig; if (config == null) { throw new Exception("AddCasClientAuth: CasConfig could not be found in the IServiceCollection."); } // determine the authentication type var authType = config.AuthType(); if (authType == AuthTypes.Service) { logger.LogInformation("authentication: application ClientId and ClientSecret (service principal)."); } if (authType == AuthTypes.Token) { logger.LogInformation("authentication: managed identity with failback to az cli."); } // load the configuration logger.LogInformation("Loading configuration..."); await config.Apply(); // confirm and log the configuration config.Optional("AUTH_TYPE", authType.ToString()); config.Optional("AUTH_TYPE_CONFIG", config.AuthType("CONFIG").ToString()); if (authType == AuthTypes.Service) { config.Require("AZURE_TENANT_ID", CasConfig.AzureTenantId); config.Require("AZURE_CLIENT_ID", CasConfig.AzureClientId); config.Require("AZURE_CLIENT_SECRET", await config.AzureClientSecret(), hideValue: true); } config.Optional("APPCONFIG", CasConfig.AppConfig, hideIfEmpty: true); config.Optional("CONFIG_KEYS", CasConfig.ConfigKeys, hideIfEmpty: true); config.Optional("PROXY", CasConfig.Proxy, hideIfEmpty: true); config.Optional("DEFAULT_HOST_URL", CasConfig.DefaultHostUrl, hideIfEmpty: true); config.Optional("SERVER_HOST_URL", CasConfig.ServerHostUrl); config.Optional("CLIENT_HOST_URL", CasConfig.ClientHostUrl); config.Optional("WEB_HOST_URL", CasConfig.WebHostUrl); config.Optional("USE_INSECURE_DEFAULTS", CasConfig.UseInsecureDefaults, hideValue: false); config.Optional("IS_HTTPS", CasConfig.IsHttps, hideValue: false); config.Require("ISSUER", CasConfig.Issuer); config.Require("AUDIENCE", CasConfig.Audience); config.Require("ALLOWED_ORIGINS", CasConfig.AllowedOrigins); config.Require("BASE_DOMAIN", CasConfig.BaseDomain()); config.Require("WELL_KNOWN_CONFIG_URL", CasConfig.WellKnownConfigUrl); config.Optional("REQUIRE_SECURE_FOR_COOKIES", CasConfig.RequireSecureForCookies, hideValue: false); config.Optional("REQUIRE_HTTPONLY_ON_USER_COOKIE", CasConfig.RequireHttpOnlyOnUserCookie, hideValue: false); config.Optional("REQUIRE_HTTPONLY_ON_XSRF_COOKIE", CasConfig.RequireHttpOnlyOnXsrfCookie, hideValue: false); config.Optional("VERIFY_TOKEN_IN_HEADER", CasConfig.VerifyTokenInHeader, hideValue: false); config.Optional("VERIFY_TOKEN_IN_COOKIE", CasConfig.VerifyTokenInCookie, hideValue: false); config.Optional("VERIFY_XSRF_IN_HEADER", CasConfig.VerifyXsrfInHeader, hideValue: false); config.Optional("VERIFY_XSRF_IN_COOKIE", CasConfig.VerifyXsrfInCookie, hideValue: false); config.Optional("SAME_SITE", CasConfig.SameSite.ToString()); config.Optional("USER_COOKIE_NAME", CasConfig.UserCookieName); config.Optional("ROLE_FOR_ADMIN", CasConfig.RoleForAdmin); config.Optional("ROLE_FOR_SERVICE", CasConfig.RoleForService); } // add the validator service services.AddSingleton <CasTokenValidator>(); // setup authentication services .AddAuthentication("cas") .AddScheme <CasAuthenticationOptions, CasAuthenticationHandler>("cas", o => new CasAuthenticationOptions()); // setup authorization services.AddSingleton <IAuthorizationHandler, CasXsrfHandler>(); services.AddAuthorization(options => { options.AddPolicy("cas", policy => { policy.AddAuthenticationSchemes("cas"); policy.RequireAuthenticatedUser(); policy.Requirements.Add(new CasXsrfRequirement()); }); options.AddPolicy("cas-no-xsrf", policy => { policy.AddAuthenticationSchemes("cas"); policy.RequireAuthenticatedUser(); }); options.DefaultPolicy = options.GetPolicy("cas"); }); // setup CORS policy if (CasConfig.AllowedOrigins?.Length > 0) { services.AddCors(options => { options.AddPolicy("cas-client", builder => { builder.WithOrigins(CasConfig.AllowedOrigins) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); } }