.Net 6 Blazor Server-Side Azure ADB2C Sign Out Issue - azure-ad-b2c

I was starting a brand new project using .Net 5 and Azure ADB2C, but then .Net 6 came out so we decided to go ahead and re-create the small amount of code we had in .Net 6. One thing that was working but now it isn't anymore is the Sign Out functionality. I made no changes on the Azure AD B2C side of things.. On the .Net 5 project all I have to do is send the user to "MicrosoftIdentity/Account/SignOut" to sign them out and that was working as long as I had the following code to send the user back to the home page (sign in page) automatically:
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// Configures the Sign Out action to redirect back to the home page, which will navigate to the login page
options.Events.OnSignedOutCallbackRedirect = async context =>
{
context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
};
}
On the .Net 6 version I kept all my code very similarly (even the packages are the same) so I'm not sure why that URL stopped working. After doing some research I found this MS documentation: https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect#send-a-sign-out-request to create a special URL with Azure ADB2C for "end_session_endpoint" but when I navigate to it, it doesn't do anything besides redirecting me back to the application still signed in.
Here are the Packages I'm using:
<PackageReference Include="Microsoft.Graph" Version="4.10.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.20.0" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.20.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
And here's a sample of what I have on my Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Set static properties to hold Configuration and Environment
ConfigurationHelper.Configuration = builder.Configuration;
ConfigurationHelper.Environment = builder.Environment;
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAdB2C"));
builder.Services.AddControllersWithViews().AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor().AddMicrosoftIdentityConsentHandler();
var app = builder.Build();
...

After fixing other unrelated issues, sending the user to the URL "MicrosoftIdentity/Account/SignOut" is working. As I understand the Microsoft.Identity.Web.UI is running the sign out code for me, and the code I have on startup can hookup the sign out callback event to redirect the user. I still think it's weird that the Azure AD B2C "end_session_endpoint" wasn't working for me but maybe it's something related to how Microsoft.Identity.Web manages the session.

Related

Azure Active Directory Token URL Path?

I have a Single Page Application that communicates with a Web API that I am attempting to secure using Azure Active Directory. I've done this before using a local SQL database with local accounts and OAuth2 but I've never done it with Azure Active Directory.
I created a new MVC Web API project in Visual Studio 2017 and set the authentication part to "Cloud - Single Organization" and entered the corresponding domain. The API had me sign in during the creation process. The login information I provided was entered into the Web.Config file.
<appSettings>
<add key="ida:Tenant" value="domain.net" />
<add key="ida:Audience" value="domain.net/WebAPI" />
<add key="ida:ClientID" value="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />
<add key="ida:Password" value="XXXXXXXXXXXXXXXXXXXXXXXXXXX" />
</appSettings>
Then in the new Startup.Auth.cs file I have the following:
public partial class Startup
{
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
}
I don't see a path that I can call in there to initiate a login. In my original project it was the /Token path which is shown below from my original Startup.Auth.cs file.
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(12),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
};
In my original project in order to initiate the login I would use /Token in an Ajax call and pass along the username and password.
$.ajax({
url: url.getURL() + '/token',
method: 'POST',
data: {
username: username,
password: password,
grant_type: 'password'
},
How do I initiate the login in the new Web API which is using Azure Active Directory? I don't see a /token path or anything similar in the Startup.Auth.cs file. Is this something I have to add or is the project setup to automatically use the login credentials I entered when the project was first created?
When tying a Web API into Azure Active Directory there is no "/token" URL to call to initiate a login. You have to direct the users to the following website:
http://login.microsoftonline.com/{domain}/oauth2/v2.0/authorize?client_id={client_id}&response_type=id_token&scope=openid&response_mode=fragment&state=12345&nonce=678910
Make sure you enter the actual domain and client_id variables for your app which are listed in Azure. Also, in Azure, make sure the Reply URLs are set. The API will redirect back to your site after the login is complete. This does work for localhost.

How to set mailSettings from Azure App Service - Application settings

The current settings for the email server is checked into version control.
<configuration>
<system.net>
<mailSettings>
<smtp deliveryMethod="network">
<network host="..." port="25" password="..." userName="..." />
</smtp>
</mailSettings>
</system.net>
</configuration>
I'd like to remove (at least the password) from version control so other developers can't (accidently) send mail over our mail server.
We're using a SAAS provider for our outbound email, because of this I cannot limit access to this server by setting up a firewall. The only authentication is using username and password provided by the SAAS provider.
Is it possible to have the mailSettings be set from the Application settings blade in Azure. This way I can remove the password from version control and do not need to manually update the web.config upon (automatic) deployment.
As you mentioned you are using Postal. On their GitHub, I notice you can provide a function that creates a SmtpClient.
Starting from there, you can create a custom SmtpClient that will be built using application settings:
var smtpServerHost = CloudConfigurationManager.GetSetting("SmtpServerHost");
var smtpServerPort = int.Parse(CloudConfigurationManager.GetSetting("SmtpServerPort"));
var smtpServerUserName = CloudConfigurationManager.GetSetting("SmtpServerUserName");
var smtpServerPassword = CloudConfigurationManager.GetSetting("SmtpServerPassword");
var client = new SmtpClient(smtpServerHost, smtpServerPort);
client.Credentials = new NetworkCredential(smtpServerUserName, smtpServerPassword);
This way you'll be able to modify your email settings in the Application settings of your Azure App Service.

Unable to get bearer token from AAD to consume Web API

I am attempting to secure my Web API applications such that only specific users and applications can consume the services. I have followed many different instructions that have suggested that I have the following code to authenticate (I have simplified this to be easily reproducible in a Console application):
class Program
{
private const string ServicesClientId = "11111111-1111-1111-1111-111111111111";
private const string ClientId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
private const string ClientKey = "abcdefghijklmnopqrstuvwxyz1234567890=";
private const string AadLoginUri = "https://login.windows.net/{0}";
private const string TenantId = "example.onmicrosoft.com";
private static readonly string Authority = string.Format(CultureInfo.InvariantCulture, AadLoginUri, TenantId);
static void Main(string[] args)
{
var clientCredential = new ClientCredential(ClientId, ClientKey);
var context = new AuthenticationContext(Authority, false);
// This line fails!
var appAuthResult = context.AcquireToken(ServicesClientId, clientCredential);
// AADSTS50105: Application 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' is not
// assigned to a role for the application '11111111-1111-1111-1111-111111111111'.
var appAuthTokenProvider = new ApplicationTokenProvider(context, "https://example.azurewebsites.net", clientCredential, appAuthResult);
var tokenCreds = new TokenCredentials(appAuthTokenProvider);
Console.WriteLine(tokenCreds.ToString());
Console.ReadLine();
}
}
So this code works beautifully as long as user assignment is disabled but the moment that user assignment is enabled (and you wait a minute as it doesn't appear to be effective instantly even though it says it was successfully enabled), it fails.
I receive the following error:
AADSTS50105: Application 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' is not
assigned to a role for the application
'11111111-1111-1111-1111-111111111111'.
What must I do to get this to work?
I have tried and tried all sorts of things to get this to go away without luck. Things I have tried:
Adding the default "Access MyWebAPI" permission from my registered client to the registered Web API project
Changing the manifest for the Web API application to add an App Role:
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Universal App Client",
"id": "c27e3fa1-e96a-445c-aaf7-8cbb60cca980",
"isEnabled": true,
"description": "Application Consuming all Services.",
"value": "AppClient"
}
and then setting the Application Permission (on the registered consuming application) to have this new "Universal App Client" permission.
Changing the manifest for the Web API application to add the consuming application's Client ID under the knownClientApplications array.
I have reprovisioned my Web API application several times in case I goofed it up via the troubleshooting process but I always end up
Patting my head while rubbing my stomach.
I'm not sure what to try next.
For reference purposes, here is my packages.config file:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.28.1" targetFramework="net46" />
<package id="Microsoft.Rest.ClientRuntime" version="1.8.2" targetFramework="net46" />
<package id="Microsoft.Rest.ClientRuntime.Azure.Authentication" version="0.11.3" targetFramework="net46" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net46" />
</packages>
This question is similar to this other question but the answer to that question (reprovision & redeploy it) does not work for me, as mentioned above.
I could reproduce this issue too. I found when I grant the application role the web API, the role maybe not granted as expected. Here is figure for your reference:
As a workaround to limit the users and applications, we can code it in the web API to implement the token handler ourselves. For example, we can config the allowed users, applications and then parse the access token to retrieve the appid and upn to verify it. More detail about custom token handler, you can refer this thread.
And for the original issue, I am also trying to report it internally.

How do I get my console app to have update access to my Sharepoint Online site?

In Module 2 of the MVA course Building Blocks and Services of the SharePoint Platform titled ‘Deep Dive into SharePoint Lists for Data Storage’ (at about 45 minutes in) Ted Pattison did a demo of using a console app to create a list on a SharePoint Online site. The course is at http://www.microsoftvirtualacademy.com/training-courses/deep-dive-building-blocks-and-services-of-sharepoint
I’m trying to do the same in my environment but I’m having trouble.
In the demo he went to _layouts/15/AppRegNew.aspx to register a new app in the app registry. In the demo, at the top of the page there was an ‘App Type’ radio Button list with the options ‘An app running on the web Server’ and ‘An App running on a client machine’. When I access this page on my site, there is no such radio Button List. Also in the demo, Ted left the Redirect URL blank. On my site it is required:
So to get past this I entered the URL for my site (https://mydomain.sharepoint.com/sites/test). The app ID was created successfully:
I then went to _layouts/15/AppInv.aspx to give the app security. I pasted in the CAML to give the app read access to the web:
<AppPermissionRequests>
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="Read" />
</AppPermissionRequests>
And then trusted the app by Clicking Trust It:
I then copied the values from the App registration into my app.config:
<add key="targetSiteUrl" value="https://xxxxx.sharepoint.com/sites/test"/>
<add key="clientId" value="bf4c37ef-9202-41ba-8430-3983cba26285"/>
<add key="clientSecret" value="nKGefHSvT69Ls2rwq1AIVyyHkIwlBzT9UkpbJMUcIbw="/>
<add key="deleteOnly" value="false"/>
And then created code based on what was in the demo to get the webs title:
static void Main(string[] args)
{
string siteUrl = ConfigurationManager.AppSettings["targetSiteUrl"];
bool deleteOnly = ConfigurationManager.AppSettings["deleteOnly"].Equals("true");
Uri siteUri = new Uri(siteUrl);
string realm = TokenHelper.GetRealmFromTargetUrl(siteUri);
var accessToken = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal,
siteUri.Authority, realm).AccessToken;
using (var clientContext = TokenHelper.GetClientContextWithAccessToken(siteUrl, accessToken)) {
var web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
Console.WriteLine(web.Title);
}
}
The code above gets the realm and the access token and successfully creates the clientContext, But when I run the executeQuery I always get the error Microsoft.SharePoint.Client.ServerUnauthorizedAccessException. I have tried giving the app ID full control of the Web, the Site Collection and the Tenant, but I still get the same error.
How do I get my console app to have update access to my site?
I needed to set AllowAppOnlyPolicy when adding the permissions in appinv.aspx
<AppPermissionRequests AllowAppOnlyPolicy="true" >

Unable to authenticate to ASP.NET Web Api service with HttpClient

I have an ASP.NET Web API service that runs on a web server with Windows Authentication enabled.
I have a client site built on MVC4 that runs in a different site on the same web server that uses the HttpClient to pull data from the service. This client site runs with identity impersonation enabled and also uses windows authentication.
The web server is Windows Server 2008 R2 with IIS 7.5.
The challenge I am having is getting the HttpClient to pass the current windows user as part of its authentication process. I have configured the HttpClient in this manner:
var clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
clientHandler.PreAuthenticate = true;
clientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var httpClient = new HttpClient(clientHandler);
My understanding is that running the site with identity impersonation enabled and then building the client in this manner should result in the client authenticating to the service using the impersonated identity of the currently logged in user.
This is not happening. In fact, the client doesn't seem to be authenticating at all.
The service is configured to use windows authentication and this seems to work perfectly. I can go to http://server/api/shippers in my web browser and be prompted for windows authentication, once entered I receive the data requested.
In the IIS logs I see the API requests being received with no authentication and receiving a 401 challenge response.
Documentation on this one seems to be sparse.
I need some insight into what could be wrong or another way to use windows authentication with this application.
Thank You,
Craig
I have investigated the source code of HttpClientHandler (the latest version I was able to get my hands on) and this is what can be found in SendAsync method:
// BeginGetResponse/BeginGetRequestStream have a lot of setup work to do before becoming async
// (proxy, dns, connection pooling, etc). Run these on a separate thread.
// Do not provide a cancellation token; if this helper task could be canceled before starting then
// nobody would complete the tcs.
Task.Factory.StartNew(startRequest, state);
Now if you check within your code the value of SecurityContext.IsWindowsIdentityFlowSuppressed() you will most probably get true. In result the StartRequest method is executed in new thread with the credentials of the asp.net process (not the credentials of the impersonated user).
There are two possible ways out of this. If you have access to yours server aspnet_config.config, you should set following settings (setting those in web.config seems to have no effect):
<legacyImpersonationPolicy enabled="false"/>
<alwaysFlowImpersonationPolicy enabled="true"/>
If you can't change the aspnet_config.config you will have to create your own HttpClientHandler to support this scenario.
UPDATE REGARDING THE USAGE OF FQDN
The issue you have hit here is a feature in Windows that is designed to protect against "reflection attacks". To work around this you need to whitelist the domain you are trying to access on the machine that is trying to access the server. Follow below steps:
Go to Start --> Run --> regedit
Locate HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0 registry key.
Right-click on it, choose New and then Multi-String Value.
Type BackConnectionHostNames (ENTER).
Right-click just created value and choose Modify.
Put the host name(s) for the site(s) that are on the local computer in the value box and click OK (each host name/FQDN needs to be on it's own line, no wildcards, the name must be exact match).
Save everything and restart the machine
You can read full KB article regarding the issue here.
I was also having this same problem. Thanks to the research done by #tpeczek, I developed the following solution: instead of using the HttpClient (which creates threads and sends requests async,) I used the WebClient class which issues requests on the same thread. Doing so enables me to pass on the user's identity to WebAPI from another ASP.NET application.
The obvious downside is that this will not work async.
var wi = (WindowsIdentity)HttpContext.User.Identity;
var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}
Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
The reason why this is not working is because you need double hop authentication.
The first hop is the web server, getting impersonation with Windows authentication to work there is no problem. But when using HttpClient or WebClient to authenticate you to another server, the web server needs to run on an account that has permission to do the necessary delegation.
See the following for more details:
http://blogs.technet.com/b/askds/archive/2008/06/13/understanding-kerberos-double-hop.aspx
Fix using the "setspn" command:
http://www.phishthis.com/2009/10/24/how-to-configure-ad-sql-and-iis-for-two-hop-kerberos-authentication-2/
(You will need sufficient access rights to perform these operations.)
Just consider what would happen if any server was allowed to forward your credentials as it pleases... To avoid this security issue, the domain controller needs to know which accounts are allowed to perform the delegation.
To impersonate the original (authenticated) user, use the following configuration in the Web.config file:
<authentication mode="Windows" />
<identity impersonate="true" />
With this configuration, ASP.NET always impersonates the authenticated user, and all resource access is performed using the authenticated user's security context.

Resources