Update UserIdentities with roles after Azure AD B2C login - asp.net

I have a Blazor WASM application communicating with a ASP.NET 6 Web API.
User authentication is done via Azure AD B2C by attaching the AD token to Http requests sent to the Server using
builder.Services.AddHttpClient("Portal.ServerAPI", client => client.BaseAddress = new Uri("https://localhost:7001/api/"))
.AddHttpMessageHandler<SslAuthorizationMessageHandler>();
User specific information like UserRoles is stored in a user database.
I'm using the RemoteAuthenticatorView.OnLoginSuceeded handler to load the user profile containing the roles from the API server.
Then I add a new identity to the existing ClaimsPrincipal which I get from the AuthenticationStateProvider like so:
var state = await authStateProvider.GetAuthenticationStateAsync();
var user = state.User;
if (user.Identities.Any(x => x.Label == "myAuthToken"))
{
return;
}
// Turn the JWT token into a ClaimsPrincipal
var principal = tokenService.GetClaimsPrincipal(sslToken);
var identity = new ClaimsIdentity(principal.Identity);
identity.Label = "myAuthToken";
user.AddIdentity(identity);
Not sure if that's the right way to do this but it works fine.
Now my problem:
When I refresh the page by hitting F5 in the browser the above handler is not called and the roles are not written to the new identity, means user.IsInRole("myRole") doesn't work.
Does anyone have an idea how to solve the issue of enriching an existing user identity on Blazor with roles coming from the server?
Any help is much appreciated.

Related

Auth setup of B2C Web API accessing confidential client (multitenant) Web API

I have a multi-tenant Web API of tenant A. It has permissions exposed and accepted by a B2C Web API of tenant B. (The API App Services live in the same tenant, but their AD instances are separate due to the one being a B2C tenant).
I have the following code in my B2C Web API authenticating with tenant B to access the multi-tenant Web API of tenant A.
I'm using Microsoft.Identity.Web (v1.25.5) and .NET Core (6), and so I don't have to handle making unnecessary calls to get an access token, I'm using the IDownstreamWebApi helper classes (though I have tried without according to the documentation, but land up with the same error.)
My code:
appsettings.json
program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
builder.Configuration.Bind("AzureAdB2C", options);
},
options => {
builder.Configuration.Bind("AzureAdB2C", options);
})
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
builder.Configuration.Bind("AzureAdB2C", options);
})
.AddDownstreamWebApi("TenantAApi", options =>
{
builder.Configuration.Bind("TenantAApi", options);
})
.AddInMemoryTokenCaches();
Calling code:
var response = await _downstreamWebApi.CallWebApiForAppAsync(
"TenantAApi",
options =>
{
options.HttpMethod = httpMethod;
options.RelativePath = url;
}, content);
var responseContent = await response.Content.ReadAsStringAsync();
The error I receive:
MSAL.NetCore.4.48.0.0.MsalClientException:
ErrorCode: tenant_override_non_aad
Microsoft.Identity.Client.MsalClientException: WithTenantId can only be used when an AAD authority is specified at the application level.
at Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder`1.WithTenantId(String tenantId)
at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(String scope, String authenticationScheme, String tenant, TokenAcquisitionOptions tokenAcquisitionOptions)
at Microsoft.Identity.Web.DownstreamWebApi.CallWebApiForAppAsync(String serviceName, String authenticationScheme, Action`1 downstreamWebApiOptionsOverride, StringContent content)
What doesn't make sense is that I'm calling this from a B2C Web API, from what I can see in the existing AbstractAcquireTokenParameterBuilder code (see line 292), B2C authorities are not AAD specific, and even so, adding an Authority or AadAuthorityAudience to my AzureAdB2C config object has no effect.
Am I missing a configuration property somewhere?
It seems that this isn't possible according to the following wiki post -
https://github.com/AzureAD/microsoft-identity-web/wiki/b2c-limitations#azure-ad-b2c-protected-web-apis-cannot-call-downstream-apis
For now I'm going to try a different approach and get an access token with a ConfidentialClientApplication object, and if that doesn't work, create a separate app registration in the other tenant and authenticate with that instead.

Created a mvc5 app with Identity2, how do i set it up to use session cookies, so they expire when the browser closes

Created a mvc5 app with Identity2,using google login (pretty much the empty app, with google stuff turned on)
How do I set it up to use session cookies, so they expire when the browser closes.
The app will be used by students who may hot swap seats, so i need the login to expire when the browser closes.
I read an SO article that implies this is the default, but when i close the browser, and go back to the site, it remembers the google login.
Edit
Sorry to burst everyone bubble, but this isn't a duplicate.
It reproduced in Chrome after the settings in the supposed "answer" are changed, and it also reproduces in IE... This is an Asp.net Identity 2 +Google login issue, not a Chrome issue.
Edit
Adding Startup Auth file for Setup Help
using System;
using System.Configuration;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Google;
using Owin;
using StudentPortalGSuite.Models;
namespace StudentPortalGSuite
{
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes( 30 ),
regenerateIdentity: ( manager, user ) => user.GenerateUserIdentityAsync( manager )
)
},
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// per https://learn.microsoft.com/en-us/aspnet/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on - EWB
//dev-jcsn email
app.UseGoogleAuthentication( new GoogleOAuth2AuthenticationOptions()
{
ClientId = "...",
ClientSecret = "..."
} );
//});
}
}
}
EDIT
The use case I'm trying to fix is, since our app is used in a classroom, that student A Closes his/her browser instead of logging out, and then next user tries to login. As it stands they are autologged into user A's account.
I'd also be up for a way to 100% log out the user when redirected to the login page, but all the ways I've tried that aren't working.
Maybe you can catch the window close event on page and call logout method
$(window).on("beforeunload", function() {
//ajax call to a post controller that logs the user out
})
Calling this at the top of the LogIn controller Method solved the issue.
Request.GetOwinContext().Authentication.SignOut( DefaultAuthenticationTypes.ApplicationCookie );// https://stackoverflow.com/questions/28999318/owin-authentication-signout-doesnt-seem-to-remove-the-cookie - stralos s answer
Request.GetOwinContext().Authentication.SignOut( DefaultAuthenticationTypes.ExternalCookie );

DropboxServiceProvider api with .Net

Trying to use Spring Net Social Dropbox
OAuthToken oauthToken = dropboxServiceProvider.OAuthOperations.FetchRequestTokenAsync(callBackUrl, null).Result;
Console.WriteLine("Done");
OAuth1Parameters parameters = new OAuth1Parameters();
parameters.Add("locale", CultureInfo.CurrentUICulture.IetfLanguageTag); // for a localized version of the authorization website
string authenticateUrl = dropboxServiceProvider.OAuthOperations.BuildAuthorizeUrl(oauthToken.Value, parameters);
Console.WriteLine("Redirect user for authorization");
Process.Start(authenticateUrl);
After redirecting user to authenticate him with dropbox how to get the request access token as I am the request would be going to call back url.
Can I create new instance of OAuthToken and new instance of dropboxserviceprovider and use it to get the access token.
AuthorizedRequestToken requestToken = new AuthorizedRequestToken(oauthToken, null);
OAuthToken oauthAccessToken = dropboxServiceProvider.OAuthOperations.ExchangeForAccessTokenAsync(requestToken, null).Result;
Console.WriteLine("Done");
/* API */
Console.WriteLine(oauthAccessToken.Value);
Console.WriteLine(oauthAccessToken.Secret);
IDropbox dropbox = dropboxServiceProvider.GetApi(oauthAccessToken.Value, oauthAccessToken.Secret);
You can store the access token in the session.
You can create a DropboxServiceProvider any time you need, what's important is the oauth access token.
Take a look to the MVC quickstart provided in the package.

DotNetOpenAuth Failing to work on Live Server

I worked on a sample application integrating OpenID into ASP.NET Web Forms. It works fine when hosted locally on my machine. However, when I uploaded the application to a live server, it started giving "Login Failed".
You can try a sample here: http://samples.bhaidar.net/openidsso
Any ideas?
Here is the source code that fails to process the OpenID response:
private void HandleOpenIdProviderResponse()
{
// Define a new instance of OpenIdRelyingParty class
using (var openid = new OpenIdRelyingParty())
{
// Get authentication response from OpenId Provider Create IAuthenticationResponse instance to be used
// to retreive the response from OP
var response = openid.GetResponse();
// No authentication request was sent
if (response == null) return;
switch (response.Status)
{
// If user was authenticated
case AuthenticationStatus.Authenticated:
// This is where you would look for any OpenID extension responses included
// in the authentication assertion.
var fetchResponse = response.GetExtension<FetchResponse>();
// Store the "Queried Fields"
Session["FetchResponse"] = fetchResponse;
// Use FormsAuthentication to tell ASP.NET that the user is now logged in,
// with the OpenID Claimed Identifier as their username.
FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false);
break;
// User has cancelled the OpenID Dance
case AuthenticationStatus.Canceled:
this.loginCanceledLabel.Visible = true;
break;
// Authentication failed
case AuthenticationStatus.Failed:
this.loginFailedLabel.Visible = true;
break;
}
}
As Andrew suggested, check the exception. In my case, my production server's time & date were off and it wouldn't authenticate because the ticket expired.
Turn on logging on your live server and inspect them for additional diagnostics. It's most likely a firewall or permissions problem on your server that prevents outbound HTTP requests.
You may also find it useful to look at the IAuthenticationResponse.Exception property when an authentication fails for clues.

Authentication problem between ASP.Net Webforms app and a ASP.Net webservice

I got a ASP.Net webforms app which is using Forms authentication. It needs to authenticate against a webservice which uses windows authentication (+ impersonation).
I've tried (amongst other things) to supply credentials by using:
service.Credentials = new NetworkCredential(myUserName, thePassword, theDomain)
The problem is that I get 401 from the webservice no matter what I try.
I am doing exactly the same as this (by the sounds of it) and here is the way I construct the credentials.
var service = new MyService();
var netCredential = new NetworkCredential("user", "pwd", "domain");
var credentialCache = new CredentialCache
{
{new Uri(service.Url), "Basic", netCredential}
};
service.Credentials = credentialCache;
The problem was that identity that the Application Pool that the Web service run on wasn't allowed to impersonate.

Resources