Quantcast
Viewing all articles
Browse latest Browse all 96

Secure ASP.NET Core MVC with Angular using IdentityServer4 OpenID Connect Hybrid Flow

This article shows how an ASP.NET Core MVC application using Angular in the razor views can be secured using IdentityServer4 and the OpenID Connect Hybrid Flow. The user interface uses server side rendering for the MVC views and the Angular app is then implemented in the razor view. The required security features can be added to the application easily using ASP.NET Core, which makes it safe to use the OpenID Connect Hybrid flow, which once authenticated and authorised, saves the token in a secure cookie. This is not an SPA application, it is an ASP.NET Core MVC application with Angular in the razor view. If you are implementing an SPA application, you should use the OpenID Connect Implicit Flow.

Code: https://github.com/damienbod/AspNetCoreMvcAngular

IdentityServer4 configuration for OpenID Connect Hybrid Flow

IdentityServer4 is implemented using ASP.NET Core Identity with SQLite. The application implements the OpenID Connect Hybrid flow. The client is configured to allow the required scopes, for example the ‘openid’ scope must be added and also the RedirectUris property which implements the URL which is implemented on the client using the ASP.NET Core OpenID middleware.

using IdentityServer4;
using IdentityServer4.Models;
using System.Collections.Generic;

namespace QuickstartIdentityServer
{
    public class Config
    {
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
                new IdentityResource("thingsscope",new []{ "role", "admin", "user", "thingsapi" } )
            };
        }

        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("thingsscope")
                {
                    ApiSecrets =
                    {
                        new Secret("thingsscopeSecret".Sha256())
                    },
                    Scopes =
                    {
                        new Scope
                        {
                            Name = "thingsscope",
                            DisplayName = "Scope for the thingsscope ApiResource"
                        }
                    },
                    UserClaims = { "role", "admin", "user", "thingsapi" }
                }
            };
        }

        // clients want to access resources (aka scopes)
        public static IEnumerable<Client> GetClients()
        {
            // client credentials client
            return new List<Client>
            {
                new Client
                {
                    ClientName = "angularmvcmixedclient",
                    ClientId = "angularmvcmixedclient",
                    ClientSecrets = {new Secret("thingsscopeSecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.Hybrid,
                    AllowOfflineAccess = true,
                    RedirectUris = { "https://localhost:44341/signin-oidc" },
                    PostLogoutRedirectUris = { "https://localhost:44341/signout-callback-oidc" },
                    AllowedCorsOrigins = new List<string>
                    {
                        "https://localhost:44341/"
                    },
                    AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OfflineAccess,
                        "thingsscope",
                        "role"

                    }
                }
            };
        }
    }
}

MVC Angular Client Configuration

The ASP.NET Core MVC application with Angular is implemented as shown in this post: Using Angular in an ASP.NET Core View with Webpack

The cookie authentication middleware is used to store the access token in a cookie, once authorised and authenticated. The OpenIdConnectAuthentication middleware is used to redirect the user to the STS server, if the user is not authenticated. The SaveTokens property is set, so that the token is persisted in the secure cookie.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
	AuthenticationScheme = "Cookies"
});

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
	AuthenticationScheme = "oidc",
	SignInScheme = "Cookies",

	Authority = "https://localhost:44348",
	RequireHttpsMetadata = true,

	ClientId = "angularmvcmixedclient",
	ClientSecret = "thingsscopeSecret",

	ResponseType = "code id_token",
	Scope = { "openid", "profile", "thingsscope" },

	GetClaimsFromUserInfoEndpoint = true,
	SaveTokens = true
});

The Authorize attribute is used to secure the MVC controller or API.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreMvcAngular.Controllers
{
    [Authorize]
    public class HomeController : Microsoft.AspNetCore.Mvc.Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

CSP: Content Security Policy in the HTTP Headers

Content Security Policy helps you reduce XSS risks. The really brilliant NWebSec middleware can be used to implement this as required. Thanks to André N. Klingsheim for this excellent library. The middleware adds the headers to the HTTP responses.

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

In this configuration, mixed content is not allowed and the scripts can only be used from a local source. Unsafe inline styles are allowed.

app.UseCsp(opts => opts
	.BlockAllMixedContent()
	.ScriptSources(s => s.Self())
	.StyleSources(s => s.UnsafeInline())
);

Set the Referrer-Policy in the HTTP Header

This allows us to restrict the amount of information being passed on to other sites when referring to other sites.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy

Scott Helme write a really good post on this:
https://scotthelme.co.uk/a-new-security-header-referrer-policy/

Again NWebSec middleware is used to implement this.

app.UseReferrerPolicy(opts => opts.NoReferrer());

Secure Cookies

Only secure cookies should be used to store the session information. The anti-forgery cookie is an exception to this.

You can check this in the Chrome browser:

Image may be NSFW.
Clik here to view.

XFO: X-Frame-Options

The X-Frame-Options Headers can be used to prevent an IFrame from being used from within the UI. This helps protect against click jacking.

https://developer.mozilla.org/de/docs/Web/HTTP/Headers/X-Frame-Options

app.UseXfo(xfo => xfo.Deny());

Configuring HSTS: Http Strict Transport Security

The HTTP Header tells the browser to force HTTPS for a length of time.

app.UseHsts(hsts => hsts.MaxAge(365).IncludeSubdomains());

TOFU (Trust on first use) or first time loading.

Once you have a proper cert and a fixed URL, you can configure that the browser to preload HSTS settings for your website.

https://hstspreload.org/

https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet

X-Xss-Protection NWebSec

Adds a middleware to the ASP.NET Core pipeline that sets the X-Xss-Protection (Docs from NWebSec)

 app.UseXXssProtection(options => options.EnabledWithBlockMode());

CORS

Only the allowed CORS should be enabled when implementing this. Disabled this as much as possible.

Validating the security Headers

Once you start the application, you can check that all the security headers are added as required:

Image may be NSFW.
Clik here to view.

Here’s the Configure method with all the NWebsec app settings as well as the authentication middleware for the client MVC application.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging"));
	loggerFactory.AddDebug();
	loggerFactory.AddSerilog();

	//Registered before static files to always set header
	app.UseHsts(hsts => hsts.MaxAge(365).IncludeSubdomains());
	app.UseXContentTypeOptions();
	app.UseReferrerPolicy(opts => opts.NoReferrer());

	app.UseCsp(opts => opts
		.BlockAllMixedContent()
		.ScriptSources(s => s.Self())
		.StyleSources(s => s.UnsafeInline())
	);

	JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/Home/Error");
	}

	app.UseCookieAuthentication(new CookieAuthenticationOptions
	{
		AuthenticationScheme = "Cookies"
	});

	app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
	{
		AuthenticationScheme = "oidc",
		SignInScheme = "Cookies",

		Authority = "https://localhost:44348",
		RequireHttpsMetadata = true,

		ClientId = "angularmvcmixedclient",
		ClientSecret = "thingsscopeSecret",

		ResponseType = "code id_token",
		Scope = { "openid", "profile", "thingsscope" },

		GetClaimsFromUserInfoEndpoint = true,
		SaveTokens = true
	});

	var angularRoutes = new[] {
		 "/default",
		 "/about"
	 };

	app.Use(async (context, next) =>
	{
		if (context.Request.Path.HasValue && null != angularRoutes.FirstOrDefault(
			(ar) => context.Request.Path.Value.StartsWith(ar, StringComparison.OrdinalIgnoreCase)))
		{
			context.Request.Path = new PathString("/");
		}

		await next();
	});

	app.UseDefaultFiles();
	app.UseStaticFiles();

	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/Home/Error");
	}

	app.UseStaticFiles();

	//Registered after static files, to set headers for dynamic content.
	app.UseXfo(xfo => xfo.Deny());
	app.UseRedirectValidation(); //Register this earlier if there's middleware that might redirect.
	app.UseXXssProtection(options => options.EnabledWithBlockMode());

	app.UseMvc(routes =>
	{
		routes.MapRoute(
			name: "default",
			template: "{controller=Home}/{action=Index}/{id?}");
	});
}

Links:

https://www.scottbrady91.com/OpenID-Connect/OpenID-Connect-Flows

https://docs.nwebsec.com/en/latest/index.html

https://www.nwebsec.com/

https://github.com/NWebsec/NWebsec

https://content-security-policy.com/

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy

https://scotthelme.co.uk/a-new-security-header-referrer-policy/

https://developer.mozilla.org/de/docs/Web/HTTP/Headers/X-Frame-Options

https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet

https://gun.io/blog/tofu-web-security/

https://en.wikipedia.org/wiki/Trust_on_first_use

http://www.dotnetnoob.com/2013/07/ramping-up-aspnet-session-security.html

http://openid.net/specs/openid-connect-core-1_0.html

https://www.ssllabs.com/


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 96

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>