Beispiel #1
0
        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();
        }
Beispiel #2
0
        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);
                    }
                }
            };
        }
Beispiel #3
0
        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");
        }
Beispiel #4
0
        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));
            }
        }
Beispiel #7
0
        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();
                    });
                });
            }
        }