Customising Azure B2C Login UI - azure

I've created a simple template like in the demos:
<!DOCTYPE html>
<html>
<head>
<title>Add your title here</title>
</head>
<body>
<h1>My B2C Application</h1>
<div id="api"></div> <!-- Leave this element empty because Azure AD B2C will insert content here. -->
</body>
</html>
This is a file hosted on my App Service at:
https://<mydomain>.azurewebsites.net/html.signin.html
I've created a B2C tennent and that is all working. In my Signup Signin policy (B2C_1_aa-signup-signin)
I've setup the custom page url to:
https://<mydomain>.azurewebsites.net/html.signin.html
However, even just clicking on the Signup button on my site returns an error:
Error. An error occurred while processing your request.
Request ID: |258cfa9e-4dab035257ed702a. Development Mode
Swapping to Development environment will display more detailed
information about the error that occurred.
Development environment should not be enabled in deployed
applications, as it can result in sensitive information from
exceptions being displayed to end users. For local debugging,
development environment can be enabled by setting the
ASPNETCORE_ENVIRONMENT environment variable to Development, and
restarting the application.
I've setup CORS in my .NET Core 2.1 application with:
services.AddCors();
And:
app.UseCors(opt =>
{
opt.WithOrigins("https://<b2c-tennent-name>.b2clogin.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
From all I've read, this is all I should have to do. I've been looking to try and find any CORS settings on my web sites App Service but I can't see anything relevant.
My site config is:
"AzureAdB2C": {
"Instance": "https://<b2c-tennent-name>.b2clogin.com/tfp/",
"ClientId": "<my id>",
"CallbackPath": "/signin-oidc",
"Domain": "<b2c-tennent-name>.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_aa-signup-signin",
"ResetPasswordPolicyId": "B2C_1_aa-password-reset",
"EditProfilePolicyId": "B2C_1_aa-profile-edit"
},
I've also enabled App Insights on my web app but I can't even seem to get a handle on an error message to solve.
I guess I have two questions.
What should I check configuration wise next?
And how do I find out what the error is? Azure seems completely opaque on this topic. I've no idea of the error at all.
Until I get identify the problem I'm completely shooting in the dark. As far as I can tell the only way I have deviated from the docs is that my html file is hosted on my App Service which from this document is a completely legitimate place to host it.
This write up describes exactly what I've done yet I my login is completely broken.
After digging around the requests I've found this hidden in an OK 200 response:
AADB2C90006: The redirect URI 'http://<my-app-service>.azurewebsites.net/signin-oidc' provided in the request is not registered for the client id 'b850cee0-f723-47fd-8f2e-c1fa1ec21038'.
But you can only register https reply URLs so how do I fix this?

Register your reply url with HTTPS: https://<my-app-service>.azurewebsites.net/signin-oidc. Azure app services hosted under the azurewebsites.net domain provide a built-in SSL certificate.

Related

Microsoft.IdentityModel does not support a B2C issuer with 'tfp' in the URI

I am trying to run the WebApp B2C sample:
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC/1-5-B2C
When I try to login, I get the following error:
IDX40002: Microsoft.IdentityModel does not support a B2C issuer with 'tfp' in the URI. See https://aka.ms/ms-id-web/b2c-issuer for details.
If I edit the Instance to https://myHost.b2clogin.com I get:
AADSTS50011: The redirect URI 'https://myHost.b2clogin.com/1c2009bb-7e35-4a0e-9f22-xxxxxxxxx/oauth2/authresp' specified in the request does not match the redirect URIs configured for the application 'c24b0337-0bd9-45ee-8376-xxxxxxxxx'. Make sure the redirect URI sent in the request matches one added to your application in the Azure portal. Navigate to https://aka.ms/redirectUriMismatchError to learn more about how to fix this.
Edit:
These are my redirects:
I tried to reproduce the same in my environment and got the below results:
I deployed custom policy starter pack via IEF Setup App by entering my Azure B2C tenant name like below:
When I checked that in Portal, custom policies are created successfully like below:
Now I registered one Azure AD B2C app named webapp1 as below:
I added redirect Uri to the above application like below: https://localhost:44316/signin-oidc
Now, I followed the same link that you mentioned and deployed one sample B2C web application by modifying appsettings.json file like below:
"AzureAdB2C": {
"Instance": "https://sridevib2c.b2clogin.com",
"ClientId": "9986e76d-bxx7-4x6x-bxx7-3d8xxxxx9a45",
"Domain": "sridevib2c.onmicrosoft.com",
"SignedOutCallbackPath": "/signout/B2C_1A_SIGNUP_SIGNIN",
"SignUpSignInPolicyId": "B2C_1A_SIGNUP_SIGNIN",
"ResetPasswordPolicyId": "B2C_1A_PASSWORDRESET",
"EditProfilePolicyId": "B2C_1A_PROFILEEDIT" // Optional profile editing policy
//"CallbackPath": "/signin/B2C_1A_SIGNUP_SIGNIN" // defaults to /signin-oidc
},
JSON file:
When I ran the above web application, it took me to below web page:
After selecting Sign Up/In, I got the login screen like below:
When I entered my credentials, I logged in to the application successfully like below:
When I clicked on Sign Out, it showed me below screen by signing me out:
After a long research I found this article/sample, where the Microsoft.Identity.UI framework is replaced with the Microsoft.AspNetCore.Authentication.AzureADB2C.UI.
However, with the https://myDomain.b2clogin.com url I still get the same error, using the custom domain it works, that's enough for me.

FB.ui Share popup on Azure Hosted Application: The domain of this URL isn't included in the app's domains

I have an application I have been developing which is to make use of the Facebook Developers FB.ui share popup:
FB.ui({
display: 'popup',
method: 'share',
href: imgUrl,
quote: imgComment,
}, function (response) { });
This seems to work perfectly when I am developing on localhost:5001 (I have set the following in my FB Dev Application settings):
However, as soon as I publish my application to my Azure hosted site (on the other address I have marked out above) I get the following error when trying to load the FB.ui Share popup:
The URL in the Valid OAuth Redirects URIs matches exactly the location of my Application online on Azure:
so cannot figure out why I am seeing this error when trying to load the Share popup.
The URL in the Valid OAuth Redirects URIs matches exactly the location of my Application online on Azure
The error message wasn't talking about that field, but about the App Domains field.
You find that under Settings -> Basic.

azure .net core app with IS4: web api call fails with "Bearer error=invalid_token The issuer is invalid"

I have a .net Core 3.1 app running in an azure web app for containers(linux) service. This app is a web api project with an angular 9 front end. It uses Identity server 4 for authorization.
For reference I am using this clean architecture framework template for my project (the add docker support pr).
The app runs in the service just fine. The front end works and I can "log in" using ID4 and I can see that the authorization token are returned when I log in.
THE PROBLEM:
When I make a call to the backend web api from the front end angular client app I get the following error:
HTTP/1.1 401 Unauthorized
Server: Kestrel
WWW-Authenticate: Bearer error="invalid_token", error_description="The issuer 'https://*********.azurewebsites.net' is invalid"
I am tempted to add a manual setting for the IssuerUri but the identity server 4 docs recommend against doing this so I did not. Maybe having this run in docker makes it different.
I did have to add support for forwarding headers to get IS4 to work properly in startup.cs configureServices according to these docs for proxy load balancers. I had to add ASPNETCORE_FORWARDEDHEADERS_ENABLED=true to my application settings
When I compare fiddler results for the requests, I can see that the AspNetCore.Antiforgery is the same for login and web api calls but the .AspNetCore.Identity.Application value is different.
I am using nSwag to auto generate api service calls for angular if that makes a difference.
QUESTION:
can someone help me figure out why I can login but all web api requests fail with the unauthorized error above?
thanks in advance.
JK
EDIT 1
I used fiddler to get the authorization token for the request and used jwt.io to parse it. The iss value was the same as the app/web api:
"iss": "https://******.azurewebsites.net",
IS4 used this domain to log in and that worked properly. If that value is correct, is there another thing that might be wrong?
EDIT 2:
Just for more context.
My app uses in statup.cs Configure:
app.UseHsts();
app.UseHttpsRedirection();
As a result I needed to add the following code to make sure the headers get forwarded in the requests between app service's handling of the TSL, load balancer/proxy and my docker container (starup.cs ConfigureServices):
// the following is documented here:
// https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1#forward-the-scheme-for-linux-and-non-iis-reverse-proxies-1
// it is needed to run kestrel in azure app service in http with header forwarding
if (string.Equals(
Environment.GetEnvironmentVariable("ASPNETCORE_FORWARDEDHEADERS_ENABLED"),
"true", StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
}
I get the following error in the logs which confirm the same error above as an Issuer mismatch
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException:
IDX10205: Issuer validation failed. Issuer: '[PII is hidden. For more
details, see https://aka.ms/IdentityModel/PII.]'. Did not match:
validationParameters.ValidIssuer ...
I am using the following default setup for the Jwt token:
services.AddAuthentication().AddIdentityServerJwt();
If i navigate to the https://*******.azurewebsites.net/.well-known/openid-configuration/jwks I get the following JSON setting for my OIDC setup:
{
"issuer": "https://*******.azurewebsites.net",
"jwks_uri": "https://*******.azurewebsites.net/.well-known/openid-configuration/jwks",
"authorization_endpoint": "https://*******.azurewebsites.net/connect/authorize",
"token_endpoint": "https://*******.azurewebsites.net/connect/token",
"userinfo_endpoint": "https://*******.azurewebsites.net/connect/userinfo",
"end_session_endpoint": "https://*******.azurewebsites.net/connect/endsession",
"check_session_iframe": "https://*******.azurewebsites.net/connect/checksession",
"revocation_endpoint": "https://*******.azurewebsites.net/connect/revocation",
"introspection_endpoint": "https://*******.azurewebsites.net/connect/introspect",
"device_authorization_endpoint": "https://*******.azurewebsites.net/connect/deviceauthorization",
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"scopes_supported": [
"openid",
"profile",
"CleanArchitecture.WebUIAPI",
"offline_access"
],
"claims_supported": [
"sub",
"name",
....
"updated_at"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token",
"implicit",
"password",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_types_supported": [
"code",
"token",
"id_token",
"id_token token",
"code id_token",
"code token",
"code id_token token"
],
"response_modes_supported": ["form_post", "query", "fragment"],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"id_token_signing_alg_values_supported": ["RS256"],
"subject_types_supported": ["public"],
"code_challenge_methods_supported": ["plain", "S256"],
"request_parameter_supported": true
}
I compared the Issuer in this document and they are the same as the one in the token as shown decoded above.
I am still not sure how to debug this to figure out where the issuer mismatch is happening.
NOTE: I have narrowed this down a bit. All calls to the built in/default IS4 endpoints work. Its only the custom webAPI endpoints I define in my controllers that are not validating the token properly.
Any webAPI endpoint with [Authorize] attribute fails with invalid issuer
EDIT 3:
Thanks to #d_f comment I used the IS4 docs for adding local API
I added the following call to my services initialization in startu.ca configure services:
services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddLocalApiAuthentication(); // I added this line after the above line
I then changed the [Authorize] attribute at the top of my webAPI controller to:
//[Authorize]
[Authorize(IdentityServerConstants.LocalApi.PolicyName)]
However, I am still getting the same error. Only on my custom webAPI endpoints, the IS4 endpoints all work. Login works but not any web api endpoints that have [Authorize] attribute.
EDIT 4:
I removed the above settings and chnaged my services.AddAUthentication() to the following:
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi(options =>
options.ExpectedScope = "IdentityServer4");
I also tried:
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi();
I used the policy name "IdentityServer4" because it appears to be a default policy within IS4
Here is what the full context looks like:
services.AddDefaultIdentity<ApplicationUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt()
.AddLocalApi(options =>
options.ExpectedScope = "IdentityServer4");
This works locally on my machine with all these variations. Its just when run inside container in azure web app that I get the issuer failure for my custom webAPI endpoints.
SOLUTION:
I found a solution thanks to all the help here. IS4 out of the box attempts to set the ISS / Issuer automatically. This works locally but in my production environment my container run in azure web apps for containers. Azure places my container inside of another container for load balancing/proxy to handle the https encryption as well. As a result there is a difference between the auto detected IS4 issuer in my container and the azure web app URL.
By manually setting the issuer in my code the error went away and everything works.
You can do this in two places
in your appsettings.jsson like:
"IdentityServer": {
"IssuerUri": "https://yourapp.azurewebsites.net",
or in code like this:
services.AddIdentityServer(options =>
{
options.IssuerUri = "https://your.azurewebsites.net/";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Hope this helps someone else and thanks again to all who helped here
You need to capture your token and use https://jwt.ms to parse it.
According to your error message: invalid token The issuer is invalid, so you should check the iss Claims in the token to make sure it is as expected in the API The issuer matches. see here.
SOLUTION:
I found a solution thanks to all the help here. IS4 out of the box attempts to set the ISS / Issuer automatically. This works locally but in my production environment my container run in azure web apps for containers. Azure places my container inside of another container for load balancing/proxy to handle the https encryption as well. As a result there is a difference between the auto detected IS4 issuer in my container and the azure web app URL.
By manually setting the issuer in my code the error went away and everything works.
You can do this in two places
in your appsettings.jsson like:
"IdentityServer": {
"IssuerUri": "https://yourapp.azurewebsites.net",
or in code like this:
services.AddIdentityServer(options =>
{
options.IssuerUri = "https://your.azurewebsites.net/";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Hope this helps someone else and thanks again to all who helped here

Azure AD B2C: The redirect URI provided in the request is not registered for the client id... but it actually is

We have the following Azure AD B2C Application (which we will call aadb2c) with the following settings
Include web app/ web API: YES
Allow Implicit Flow: YES
Reply Url:
- https://localhost:44339/
- https://productionURL.com
- https://productionURL.com/
App ID URI (which is optional): none
Native CLient: NO
This Application is what our website https://productionURL.com uses to login it's users with azure AD B2C.
However, on production we keep on getting the error:
The redirect URI 'productionURL.com' provided in the request is not registered for the client id 'aadb2c'
According to this we should add the link to out reply url.
But as you can see above, we already included https://productionURL.com in the "Reply URL" section
of the Azure AD B2C blade.
What could be causing this error to happen? How do we resolve the redirect URI request not registered error?
It needs to be configured in the code as well and you need to make sure that the protocols match. This can also happen if there's a mismatch with the tenant ID or the app ID.
Check the B2C callback request in Chrome DevTools > Network with "Preserve log" to see what URL is being returned. This should give you insight into the problem.
As an extra measure to ensure that the protocols are matching, you can add:
if (context.ProtocolMessage.RedirectUri.Contains("http:"))
{
context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri.Replace("http:", "https:");
}
After hours looking at our code and finding no traces of the url without any protocol or any trace of "http:", we now had to look at our deployment orchestrator.
Apparently in Octopus we are deploying the app with an incorrect URI: it's missing the protocol "https://"

How can I secure simple HTML files using Azure AD?

I have a legacy static website that is just plain HTML and simple JavaScript for UI effects. There is no server side code, api, config files or anything in this website - just raw HTML files, CSS, pictures, etc.
The website will not be hosted in Azure. It will be on a local IIS server. If I pull the web site into Visual Studio, the "Configure Azure AD Authentication" wizard shows:
An incompatible authentication configuration was found in this project
().
How can I secure simple HTML files using Azure AD?
The Visual Studio "Configure Azure AD Authentication" wizard is intended for ASP.Net Web Apps and Web APIs.
In your case, what you are building is considered a "Single Page Application" or SPA. Even though you might have multiple pages, this term also applies to client side only web apps with no backend code.
For this, you should follow the Azure AD Javascript Single Page Application sample.
The gist of it is that you should us ADAL.js like shown in this sample's app.js, along the lines of:
// Configure ADAL
window.config = {
instance: 'https://login.microsoftonline.com/',
tenant: '[Enter your tenant here, e.g. contoso.onmicrosoft.com]',
clientId: '[Enter your client_id here, e.g. g075edef-0efa-453b-997b-de1337c29185]',
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
};
var authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
$errorMessage.html(authContext.getLoginError());
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// Check Login Status, Update UI
var user = authContext.getCachedUser();
if (user) {
//Do UI for authenticated user
} else {
//Show UI for unauthenticated user
}
// Register NavBar Click Handlers
$signOutButton.click(function () {
authContext.logOut();
});
$signInButton.click(function () {
authContext.login();
});
Note: There's also a Angular SPA sample.
The solution posted by Saca pointed me in the right direction, but adding the JS to every page was not a valid solution for me. There were thousands of HTML files, lots with no common JS file I could tack that ADAL code into. I would have had to find a way to insert that JS on all those pages.
My first solution was simply creating a normal .NET MVC app with the proper auth configured. Then I simply loaded this legacy content via an iFrame. This worked but was limiting for the users.
As Fei Xue mentioned in another comment, the next solution involved scrapping the iFrame but routing all requests for static files through a controller. Using this as a reference for understanding that: https://weblogs.asp.net/jongalloway/asp-net-mvc-routing-intercepting-file-requests-like-index-html-and-what-it-teaches-about-how-routing-works
The above solutions worked. However, eventually this app ended up as an Azure App Service and I simply turned on authentication at the app service level with just the pure html files.

Resources