Giter Site home page Giter Site logo

damienbod / blazor.bff.azureb2c.template Goto Github PK

View Code? Open in Web Editor NEW
62.0 5.0 8.0 4.4 MB

Blazor.BFF.AzureB2C.Template, Blazor WASM hosted in ASP.NET Core using Azure B2C BFF (server authentication) and Microsoft Graph

Home Page: https://www.nuget.org/packages/Blazor.BFF.AzureB2C.Template

License: MIT License

HTML 11.89% C# 41.57% CSS 46.20% JavaScript 0.35%
graph oauth2 azureb2c azure aspnetcore dotnetcore csp

blazor.bff.azureb2c.template's Introduction

Blazor.BFF.AzureB2C.Template

.NET NuGet Status Change log

This template can be used to create a Blazor WASM application hosted in an ASP.NET Core Web app using Azure B2C and Microsoft.Identity.Web to authenticate using the BFF security architecture. (server authentication) This removes the tokens from the browser and uses cookies with each HTTP request, response. The template also adds the required security headers as best it can for a Blazor application.

Blazor BFF Azure B2C

Features

  • WASM hosted in ASP.NET Core 8
  • BFF with Azure B2C using Microsoft.Identity.Web
  • OAuth2 and OpenID Connect OIDC
  • No tokens in the browser
  • Azure AD Continuous Access Evaluation CAE support

Other templates

Blazor BFF Azure AD

Blazor BFF OpenID Connect

Using the template

install

dotnet new install Blazor.BFF.AzureB2C.Template

run

dotnet new blazorbffb2c -n YourCompany.Bff

Use the -n or --name parameter to change the name of the output created. This string is also used to substitute the namespace name in the .cs file for the project.

Setup after installation

Add the Azure B2C App registration settings

"AzureB2C": {
	"Instance": "https://--your-domain--.b2clogin.com",
	"Domain": "[Enter the domain of your B2C tenant, e.g. contoso.onmicrosoft.com]",
	"TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
	"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
	"ClientSecret": "[Copy the client secret added to the app from the Azure portal]",
	"ClientCertificates": [
	],
	// the following is required to handle Continuous Access Evaluation challenges
	"ClientCapabilities": [ "cp1" ],
	"CallbackPath": "/signin-oidc"
	// Add your policy here
	"SignUpSignInPolicyId": "B2C_1_signup_signin", 
	"SignedOutCallbackPath": "/signout-callback-oidc"
},

Add the permissions for Microsoft Graph if required, application scopes are used due to Azure B2C

"GraphApi": {
	// Add the required Graph permissions to the Azure App registration
	"TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
	"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
	"Scopes": ".default"
	//"ClientSecret": "--in-user-secrets--"
},

Use Continuous Access Evaluation CAE with a downstream API (access_token)

Azure app registration manifest

"optionalClaims": {
	"idToken": [],
	"accessToken": [
		{
			"name": "xms_cc",
			"source": null,
			"essential": false,
			"additionalProperties": []
		}
	],
	"saml2Token": []
},

Any API call for the Blazor WASM could be implemented like this:

[HttpGet]
public async Task<IActionResult> Get()
{
  try
  {
	// Do logic which calls an API and throws claims challenge 
	// WebApiMsalUiRequiredException. The WWW-Authenticate header is set
	// using the OpenID Connect standards and Signals spec.
  }
  catch (WebApiMsalUiRequiredException hex)
  {
	var claimChallenge = WwwAuthenticateParameters
		.GetClaimChallengeFromResponseHeaders(hex.Headers);
		
	return Unauthorized(claimChallenge);
  }
}

The downstream API call could be implemented something like this:

public async Task<T> CallApiAsync(string url)
{
	var client = _clientFactory.CreateClient();

	// ... add bearer token
	
	var response = await client.GetAsync(url);
	if (response.IsSuccessStatusCode)
	{
		var stream = await response.Content.ReadAsStreamAsync();
		var payload = await JsonSerializer.DeserializeAsync<T>(stream);

		return payload;
	}

	// You can check the WWW-Authenticate header first, if it is a CAE challenge
	
	throw new WebApiMsalUiRequiredException($"Error: {response.StatusCode}.", response);
}

Use Continuous Access Evaluation CAE in a standalone app (id_token)

Azure app registration manifest

"optionalClaims": {
	"idToken": [
		{
			"name": "xms_cc",
			"source": null,
			"essential": false,
			"additionalProperties": []
		}
	],
	"accessToken": [],
	"saml2Token": []
},

If using a CAE Authcontext in a standalone project, you only need to challenge against the claims in the application.

private readonly CaeClaimsChallengeService _caeClaimsChallengeService;

public AdminApiCallsController(CaeClaimsChallengeService caeClaimsChallengeService)
{
  _caeClaimsChallengeService = caeClaimsChallengeService;
}

[HttpGet]
public IActionResult Get()
{
  // if CAE claim missing in id token, the required claims challenge is returned
  var claimsChallenge = _caeClaimsChallengeService
	.CheckForRequiredAuthContextIdToken(AuthContextId.C1, HttpContext);

  if (claimsChallenge != null)
  {
	return Unauthorized(claimsChallenge);
  }

uninstall

dotnet new uninstall Blazor.BFF.AzureB2C.Template

Development

build

https://docs.microsoft.com/en-us/dotnet/core/tutorials/create-custom-template

nuget pack content/Blazor.BFF.AzureB2C.Template.nuspec

install developement

Locally built nupkg:

dotnet new install Blazor.BFF.AzureB2C.Template.3.0.0.nupkg

Local folder:

dotnet new install <PATH>

Where <PATH> is the path to the folder containing .template.config.

Azure App registrations documentation

https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications

https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga

Credits, Used NuGet packages + ASP.NET Core 8.0 standard packages

  • NetEscapades.AspNetCore.SecurityHeaders

Links

https://github.com/AzureAD/microsoft-identity-web

blazor.bff.azureb2c.template's People

Contributors

damienbod avatar rogerdawson avatar rufer7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

blazor.bff.azureb2c.template's Issues

Template for Blazor WASM not Hosted

Hi @damienbod
Have you ever done this, or got it on the list of things to try for the future?
My app architecture is ASPNET BFF Server, and a standalone Blazor WASM app.
I have been trying to hack this template so it isn't intertwined with the WASM Hosted approach but I am struggling with it.

Upon app JS Logout(Signout command) does not display Azure AD B2C login page

Hi Damien,

VSDotNetGuy again. We see in your template, you're using a form post based logout to the server side api. However, we're using a menu drawer based Logout link that when clicked raises an event. From the event code we post to the server at \api\account\logout using HttpClient.

The server side Logout method is being called and the Signout command seems to run okay. However, we expected the app would no longer be authenticated so would display the Azure AD B2C login page upon redirect or refresh.

Does your template prompt for AD login after Signout? If not, any ideas on how to be prompted by Azure AD B2C to login after the server side Signout is completed?

Thx in advance and take care,
Bob Baldwin (aka VSDotNetGuy)

Question on App registration and difference with hosted blazor wasm

Dear Damien,
Sorry to bother with a perhaps silly question.
I am trying to figure out if I am not going to switch and app to use BFF on Azure B2C, so I came accross you template.

-In terms of app registration on the portal, how do you set this up?
-Do you have one or two app registrations (server and client)?
-It seems the auth is using the implicit flow?

Besides this, I am trying to figure out the difference between your template and the Blazor Wasm Hosted that comes from Microsoft?
Why did you put the _Host.cshtml in the server, instead of the index.html in the client that comes from the MS Blazor Hosted Wasm template?

Any further clarifitcation is welcomed.
Thank you

Correlation failed error when deployed to container apps and app service containers - missing cookies, maybe samesite issue?

Hello @damienbod ,

I watched your recent stand-up with Jon Galloway which was super interesting and I thought I'd give this BFF template a go. Locally it works great, and deployed to a standard app service plan it works fine too. However, I'm pulling my hair out trying to get it to work with Azure container apps and was hoping you could point me in the right direction to fix this. Please note, before the index page would work I had to I fix another issue with the anti-forgery cookie which was resolved with the following change:

options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

to

options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;

Once the above was resolved the index page shows fine but I unfortunately get the correlation failed error reported below when selecting the login link. Any help with this is greatly appreciated:

Issue

Correlation failed error reported when selecting the log-in link on the main page. The container app logs show the following (newest first):

[40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[15]

'.AspNetCore.Correlation.JV4ZtwsqyoQzUm0XjKPsZl5UeG7IAsHK18V9J3fEtwU' cookie not found.

[40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.Http.ResponseCookies[1]

The cookie '.AspNetCore.Correlation.JV4ZtwsqyoQzUm0XjKPsZl5UeG7IAsHK18V9J3fEtwU' has set 'SameSite=None' and must also set 'Secure'.

[40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.Http.ResponseCookies[1]

The cookie '.AspNetCore.OpenIdConnect.Nonce.CfDJ8P3OW6sbpfpEkZYp1w0JsW8f2E0rMU4me_SA7Co14Nm2S8UlmZSemDj0yeKD7P5em5sNOV_0CUKPl2JSHAmZWAqJ1GU_Ni9PMQJSPoMEaGjWmJvwp-0SRuAfja8V4tnB1FFiUapJtdRH_KCWCjo_IXrGJlpg6_7b-zkZ8xl0_GxwOSIk-gOT8150GvMagOBwG8_3W5V3M-GwnpfdQt96kA8WzYNqBVgUUdbe8N821B9ac4_NqG37gna1ubwXtF1MmlQ63XVYkMREGaGWAKY87F0' has set 'SameSite=None' and must also set 'Secure'.

To Reproduce

Enable container orchestration and deploy the BFF server to a container registry.

Create a container app (and supporting resources such as environment, app insights etc.). and deploy the image above.

On the main index page select the 'Log in' link and you will be redirected to the /MicrosoftIdentity/Account/Error page which states 'Correlation failed.'

Additional notes

Deploying to a standard app service plan works fine.

Deploying to a container based app service plan does not work either, also producing a 'correlation failed' error. This is probably the quickest path to reproducing the issue as it only takes a few minutes to create the app service.

Many Thanks,

Roger

BFF, SSO, and .AspNetCore.Cookie

Hi @damienbod and friends,

Firstly, many thanks for this template and associated articles - really appreciate your work.

I created a couple of Blazor apps with the template to test out SSO. I notice that a .AspNetCore.Cookies cookie is still added to the users browser when the second app signs on.
I think there's a hole in my understanding here because I thought only a __Host-X-XSRF-TOKEN cookie was meant to be written to the user's browser and that authn state is cached server-side?

Where this all leads to is trying to have a slick SSO experience (minimal redirects to B2C (even ones without prompts) for users between our apps (app1.mydomain.com, app2.mydomain.com, etc) and I've landed on sharing the .AspNetCore.Cookies cookie between apps with the aid of Data Protection. I think it's secure enough, but would be interested in other thoughts.

Thanks,
Grant.

Unhandled exception when logging out after logging-in on multiple browser tabs

Hi,

First of all thanks for this fantastic resource!

I build a Blazor App using the template and can login/logout with my AD B2C tenant. When I open the App in a second tab, I am already signed in (as expected). If I now sign-out in either tab, and then click on the Direct-API link in the navbar on the other tab, I get an exception (in the Blazor Client) that looks like a malformed JSON object is being parsed.

This happens in Chrome and Safari on MacOS, but (curiously) not on Firefox, there I get redirected to sign in again (as expected).

I probably did something wrong, but I tried to stay as close to the template as possible. Anyway, I am totally baffled and stuck. Obviously I can give you access to my repository with the Blazor SPA. I can be reached by email on gmail or outlook by using firstname.lastname as name (to get the Client-Secret).

This is the exception I get:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: '<' is an invalid start of a value. Path: $ | LineNumber: 1 | BytePositionInLine: 0.
System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 1 | BytePositionInLine: 0.
 ---> System.Text.Json.JsonReaderException: '<' is an invalid start of a value. LineNumber: 1 | BytePositionInLine: 0.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
   at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.Serialization.JsonConverter`1[[System.String[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
   at System.Text.Json.Serialization.JsonConverter`1[[System.String[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[String[]](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadCore[String[]](JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& state, JsonConverter converterBase)
   at System.Text.Json.JsonSerializer.ContinueDeserialize[String[]](ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack, JsonConverter converter, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.<ReadAllAsync>d__65`1[[System.String[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Net.Http.Json.HttpContentJsonExtensions.<ReadFromJsonAsyncCore>d__4`1[[System.String[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Net.Http.Json.HttpClientJsonExtensions.<GetFromJsonAsyncCore>d__13`1[[System.String[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at BlazorWasmAadB2cBffTest.Client.Pages.DirectApi.OnInitializedAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

DirectController

Hi Damien,

I've been looking at the AD B2C BFF Blazor template, it looks good, thx for great work.

I did wonder since include global anti forgery setting in Startup, why did you put [ValidateAntiForgeryToken] on DirectApiController or is this just a left over?

Thx...Bob Baldwin
aka VSDotNetGuy!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.