/// <summary>
        /// Add JWT authentication scheme and config
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <param name="appSettings">AppSettings</param>
        /// <returns>IServiceCollection</returns>
        public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, AppSettings appSettings)
        {
            IdentityModelEventSource.ShowPII = true; //Add this line
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // "Bearer"
                options.DefaultChallengeScheme    = JwtBearerDefaults.AuthenticationScheme; // "Bearer"
            }).AddJwtBearer(options =>
            {
                //options.Authority = "https://localhost:6001"; // Base-address of your identityserver
                //options.RequireHttpsMetadata = true;

                string authServerBaseUrl     = appSettings.Host?.AuthServer ?? "https://localhost:6001";
                bool isRequireHttpsMetadata  = (!string.IsNullOrEmpty(authServerBaseUrl) && authServerBaseUrl.StartsWith("https")) ? true : false;
                options.Authority            = string.IsNullOrEmpty(authServerBaseUrl) ? "https://localhost:6001" : authServerBaseUrl;
                options.RequireHttpsMetadata = isRequireHttpsMetadata;
                options.MetadataAddress      = $"{authServerBaseUrl}/.well-known/openid-configuration";          // Optional
                options.Audience             = appSettings?.AuthOptions?.Audience ?? ApiResources.MyBackendApi2; // API Resource name
                options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;                                     // The JWT security token handler allows for 5 min clock skew in default
                options.BackchannelHttpHandler = AuthMetadataUtils.GetHttpHandler();
                //options.MetadataAddress = $"{authServerBaseUrl}/.well-known/openid-configuration";

                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = (e) =>
                    {
                        // Some callback here ...
                        return(Task.CompletedTask);
                    }
                };
            });

            return(services);
        }
Beispiel #2
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient <IHttpContextAccessor, HttpContextAccessor>();

            services.AddControllers()
            .AddNewtonsoftJson()
            .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            #region Enable Authentication
            IdentityModelEventSource.ShowPII = true; //Add this line
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme    = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                //options.Authority = "https://localhost:6001"; // Base-address of your identityserver
                //options.RequireHttpsMetadata = true;

                string authServerBaseUrl     = this.Configuration["Host:AuthServer"];
                bool isRequireHttpsMetadata  = (!string.IsNullOrEmpty(authServerBaseUrl) && authServerBaseUrl.StartsWith("https")) ? true : false;
                options.Authority            = string.IsNullOrEmpty(authServerBaseUrl) ? "https://localhost:6001" : authServerBaseUrl;
                options.RequireHttpsMetadata = isRequireHttpsMetadata;
                options.Audience             = "MyBackendApi2";              // API Resource name
                options.TokenValidationParameters.ClockSkew = TimeSpan.Zero; // The JWT security token handler allows for 5 min clock skew in default
                options.BackchannelHttpHandler = AuthMetadataUtils.GetHttpHandler();

                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = (e) =>
                    {
                        // Some callback here ...
                        return(Task.CompletedTask);
                    }
                };
            });
            #endregion

            #region Enable policy-based authorization

            // Required: Role "admin"
            services.AddAuthorization(options => options.AddPolicy("AdminPolicy", policy => policy.RequireRole("admin")));
            // Required: Role "user"
            services.AddAuthorization(options => options.AddPolicy("UserPolicy", policy => policy.RequireRole("user")));
            // Required: Role "sit"
            services.AddAuthorization(options => options.AddPolicy("SitPolicy", policy => policy.RequireRole("sit")));
            // Required: Role "admin" OR "user"
            services.AddAuthorization(options => options.AddPolicy("AdminOrUserPolicy", policy => policy.RequireRole("admin", "user")));
            // Required: Department "Sales"
            services.AddAuthorization(options => options.AddPolicy("SalesDepartmentPolicy", policy => policy.RequireClaim(CustomClaimTypes.Department, "Sales")));
            // Required: Department "CRM"
            services.AddAuthorization(options => options.AddPolicy("CrmDepartmentPolicy", policy => policy.RequireClaim(CustomClaimTypes.Department, "CRM")));
            // Required: Department "Sales" AND Role "admin"
            services.AddAuthorization(options => options.AddPolicy("SalesDepartmentAndAdminPolicy",
                                                                   policy => policy.RequireClaim(CustomClaimTypes.Department, "Sales").RequireRole("admin")));
            // Required: Department "Sales" AND Role "admin" or "user"
            services.AddAuthorization(options => options.AddPolicy("SalesDepartmentAndAdminOrUserPolicy",
                                                                   policy => policy.RequireClaim(CustomClaimTypes.Department, "Sales").RequireRole("admin", "user")));
            // Required: Department "Sales" OR Role "admin"
            services.AddAuthorization(options => options.AddPolicy("SalesDepartmentOrAdminPolicy", policy => policy.RequireAssertion(
                                                                       context => context.User.Claims.Any(
                                                                           x => (x.Type.Equals(CustomClaimTypes.Department) && x.Value.Equals("Sales")) || (x.Type.Equals(ClaimTypes.Role) && x.Value.Equals("admin"))))));
            #endregion

            #region Enable custom Authorization Handlers (The registration order matters!)
            services.AddSingleton <IAuthorizationHandler, EmailDomainAuthHandler>();
            services.AddSingleton <IAuthorizationHandler, UserNameAuthHandler>();

            services.AddAuthorization(options =>
            {
                var emailDomainRequirement = new EmailDomainRequirement("fake.com");
                var userNameRequirement    = new UserNameRequirement("jblin");

                // options.InvokeHandlersAfterFailure = false; // Default: true
                options.AddPolicy("DoaminAndUsernamePolicy", policy =>
                                  policy.AddRequirements(emailDomainRequirement, userNameRequirement));
            });
            #endregion

            #region Inject AppSetting configuration

            services.Configure <AppSettings>(this.Configuration);
            #endregion

            #region HttpClient Factory
            services.AddHttpClient("AuthHttpClient",
                                   config =>
            {
                config.Timeout = TimeSpan.FromMinutes(5);
                // config.BaseAddress = new Uri("https://localhost:6001/");
                config.DefaultRequestHeaders.Add("Accept", "application/json");
            })
            .ConfigurePrimaryHttpMessageHandler(h =>
            {
                var handler = new HttpClientHandler();

                // Enable sending request to server with untrusted SSL cert
                handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
                return(handler);
            })
            .SetHandlerLifetime(TimeSpan.FromMinutes(5));     // HttpMessageHandler lifetime = 2 min

            // services.AddHttpClient<IIdentityClient, IdentityClient>().SetHandlerLifetime(TimeSpan.FromMinutes(2)) // HttpMessageHandler default lifetime = 2 min
            // .ConfigurePrimaryHttpMessageHandler(h =>
            // {
            //   var handler = new HttpClientHandler();
            //   if (this.env.IsDevelopment())
            //   {
            //       //Allow untrusted Https connection
            //       handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
            //   }
            //   return handler;
            // });
            #endregion

            #region Identity Client
            services.AddSingleton <IIdentityClient, IdentityClient>();
            #endregion

            #region Inject Cache service
            services.AddCacheServices();
            #endregion
        }
        /// <summary>
        /// Add custom authentication
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <param name="appSettings">AppSettings</param>
        /// <returns>Self</returns>
        public static IServiceCollection AddOpenIdAuthentication(this IServiceCollection services, AppSettings appSettings)
        {
            IdentityModelEventSource.ShowPII = true;

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; // "Cookies"
                options.DefaultSignInScheme       = CookieAuthenticationDefaults.AuthenticationScheme; // "Cookies"
                options.DefaultChallengeScheme    = "oidc";
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddOpenIdConnect("oidc", options =>
            {
                const string CODE_VERIFIER_KEY         = "code_verifier";
                const string CODE_CHALLENGE_KEY        = "code_challenge";
                const string CODE_CHALLENGE_METHOD_KEY = "code_challenge_method";

                // Get config values from AppSetting file
                string oidcServerBaseUrl       = appSettings?.Host.AuthServer;
                bool isRequireHttpsMetadata    = !string.IsNullOrEmpty(oidcServerBaseUrl) && oidcServerBaseUrl.StartsWith("https");
                options.Authority              = string.IsNullOrEmpty(oidcServerBaseUrl) ? "https://localhost:6001" : oidcServerBaseUrl;
                options.RequireHttpsMetadata   = isRequireHttpsMetadata;
                options.MetadataAddress        = $"{oidcServerBaseUrl}/.well-known/openid-configuration";
                options.BackchannelHttpHandler = AuthMetadataUtils.GetHttpHandler();

                options.ClientId              = "PkceCodeBackend";
                options.ClientSecret          = "secret";
                options.ResponseType          = "code";
                options.ResponseMode          = "form_post";
                options.CallbackPath          = "/signin-oidc";
                options.SignedOutCallbackPath = "/signout-callback-oidc";

                options.SaveTokens = true;
                options.Scope.Add(appSettings?.AuthOptions?.Audience);
                options.Scope.Add("offline_access"); // Get refresh token

                options.Events.OnRedirectToIdentityProvider = context =>
                {
                    // only modify requests to the authorization endpoint
                    if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        // generate code_verifier
                        var codeVerifier = CryptoRandom.CreateUniqueId(32);

                        // store codeVerifier for later use
                        context.Properties.Items.Remove(CODE_VERIFIER_KEY);
                        context.Properties.Items.Add(CODE_VERIFIER_KEY, codeVerifier);

                        // create code_challenge
                        string codeChallenge;
                        using (var sha256 = SHA256.Create())
                        {
                            var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
                            codeChallenge      = Base64Url.Encode(challengeBytes);
                        }

                        // add code_challenge and code_challenge_method to request
                        context.ProtocolMessage.Parameters.Remove(CODE_CHALLENGE_KEY);
                        context.ProtocolMessage.Parameters.Remove(CODE_CHALLENGE_METHOD_KEY);
                        context.ProtocolMessage.Parameters.Add(CODE_CHALLENGE_KEY, codeChallenge);
                        context.ProtocolMessage.Parameters.Add(CODE_CHALLENGE_METHOD_KEY, "S256");
                    }

                    return(Task.CompletedTask);
                };

                options.Events.OnAuthorizationCodeReceived = context =>
                {
                    // only when authorization code is being swapped for tokens
                    if (context.TokenEndpointRequest?.GrantType == OpenIdConnectGrantTypes.AuthorizationCode)
                    {
                        // get stored code_verifier
                        if (context.Properties.Items.TryGetValue(CODE_VERIFIER_KEY, out var codeVerifier))
                        {
                            // add code_verifier to token request
                            context.TokenEndpointRequest.Parameters.Add(CODE_VERIFIER_KEY, codeVerifier);
                        }
                    }

                    return(Task.CompletedTask);
                };
            });

            return(services);
        }