We have a static class to store certain data at the application level. We set these property values when the user logs in. These values will be used wherever they are required.
In Blazor WASM, which approach is suitable for my case above? Appreciate if can provide some sample.
Thanks.
Iam currently using a claim based approach, ( with azure B2C authentication ), where im storing the values i need into the claims:
string oid ="information to store";
(context.Principal.Identity as System.Security.Claims.ClaimsIdentity).AddClaim(new System.Security.Claims.Claim("claim_name", oid));
set the info after the login:
options.Events = new OpenIdConnectEvents
{ OnTokenValidated = async context =>
{
//SET THE INFO HERE
}
}
Retrieve it by:
public string get_info_in_claims(AuthenticationState authState)
{
string ret = "";
foreach (var cla in authState.User.Claims)
{
if (cla.Type == "claim_name")
{
ret = cla.Value;
}
}
return ret;
}
Related
I have a multitenant application that I'm restricting access to specific tenants. The code to authorize a user from specific tenants works fine and for those without access I'm throwing a SecurityTokenInvalidIssuerException. The problem is I don't know how to capture this error because the code is executed at the start of Program.cs and prior to the app.build. How do I gracefully handle this error? ...right now it's just crashing the app.
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration);
// Restrict users to specific belonging to specific tenants
UserValidator user = new UserValidator();
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.IssuerValidator = user.ValidateSpecificIssuers;
});
UserValidator.cs
public class UserValidator
{
public string ValidateSpecificIssuers(string issuer, SecurityToken securityToken,
TokenValidationParameters validationParameters)
{
var validIssuers = GetAcceptedTenantIds()
.Select(tid => $"https://login.microsoftonline.com/{tid}/v2.0");
if (validIssuers.Contains(issuer))
{
return issuer;
}
else
{
throw new SecurityTokenInvalidIssuerException("The sign-in user's account does not belong to one of the tenants that this Web App accepts users from.");
}
}
private string[] GetAcceptedTenantIds()
{
// If you are an ISV who wants to make the Web app available only to certain customers who
// are paying for the service, you might want to fetch this list of accepted tenant ids from
// a database.
// Here for simplicity we just return a hard-coded list of TenantIds.
var config = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json").Build();
var acceptedTenants = config.GetSection("Tenants").GetChildren();
string[] tenants = new string[acceptedTenants.Count()];
int i = 0;
foreach (var acceptedTenant in acceptedTenants)
{
tenants[i] = acceptedTenant.GetValue<string>("TenantId");
i++;
}
return tenants;
}
}
First I want to say I'm not SL developer. I just need to modify one legacy Silverlight 5 application.
It is using RIA services and XAP is hosted in Asp.Net page.
User on login page enters credentials and is able to select database from dropdown. Whole web is using multiple connections and user is able to select database to connect.
This selected database (or any identificator for data connection) is sent do XAP's InitParams, so I can access it from SL.
private void Application_Startup(object sender, StartupEventArgs e)
{
foreach (var item in e.InitParams)
{
Resources.Add(item.Key, item.Value);
}
var selectedConnectionString = GetInitParam("ConnectionString");
// TODO: Different way to store connection string
SetCookie("ConnectionString", selectedConnectionString);
RootVisual = new LoadingPage();
}
Currently I'm trying to use cookie to store selected database. I found it somewhere as one possible solution. But it needs to change.
Ok, then we have DomainService.
public class CommissionDomainService : LinqToEntitiesDomainService<CommissionEntitiesContext>
{
...
}
I know that I need to use CreateObjectContext to change ConnectionString in service. So I have:
protected override CommissionEntitiesContext CreateObjectContext()
{
// TODO: Different way to store connection string
string connectionStringName;
if (System.Web.HttpContext.Current.Request.Cookies["ConnectionString"] != null)
{
connectionStringName = System.Web.HttpContext.Current.Request.Cookies["ConnectionString"].Value;
}
else
{
throw new Exception("Missing connectionStringName");
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
var entityCs = new EntityConnectionStringBuilder
{
Metadata = "res://*/CommissionEntities.csdl|res://*/CommissionEntities.ssdl|res://*/CommissionEntities.msl",
Provider = connectionStringSettings.ProviderName,
ProviderConnectionString = connectionStringSettings.ConnectionString
};
return new CommissionEntitiesContext(entityCs.ConnectionString);
}
Again, I used Cookie to pass value from application to service.
But it is not the best idea. Because of cookie and because of persistance etc.
My question is, how to pass my ConnectionString value from main application to DomainService? Or Can I access some application context from service? Or maybe can I get connection string somewhere in EntitiesContext?
Ok, I did it this way.
I made selected database part of user identity. Because I'm using Owin, I just used one of Claims.
So when user logs in, I just put one claim with selected database
// build a list of claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.UserData, selectedDatabase)
};
// create the identity
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// sign in
Context.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
Then in DomainService I used Initialize and CreateObjectContext methods
private string _connectionStringName;
public override void Initialize(DomainServiceContext context)
{
// Načteme z kontextu usera zvolenou databázi
var claim = ((ClaimsIdentity)context.User.Identity).FindFirst(ClaimTypes.UserData);
_connectionStringName = claim.Value;
base.Initialize(context);
...
}
protected override CommissionEntitiesContext CreateObjectContext()
{
if (string.IsNullOrEmpty(_connectionStringName))
{
throw new Exception("Missing connectionStringName");
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[_connectionStringName];
var entityCs = new EntityConnectionStringBuilder
{
Metadata = "res://*/CommissionEntities.csdl|res://*/CommissionEntities.ssdl|res://*/CommissionEntities.msl",
Provider = connectionStringSettings.ProviderName,
ProviderConnectionString = connectionStringSettings.ConnectionString
};
return new CommissionEntitiesContext(entityCs.ConnectionString);
}
I have two Web APIs with a shared machine.key. I would like to pass the bearer token generated by the first Web API to the second Web API as a parameter (i.e. token=xxxxxxxx) and extract the identity claims (i.e userId) from it.
Is this possible? I've looked all over but there doesn't seem to be much information on parsing a text bearer token to extract claims.
Thanks.
If you're using OWIN, you could implement your own OAuthBearerAuthenticationProvider, which takes the token from the query string and sets it to the context:
internal class MyAuthProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
if (context.Token == null)
{
var value = context.Request.Query.Get("token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
You could use it in your Startup.cs like this:
public void Configuration(IAppBuilder app)
{
// All the other stuff here
var audience = "";
var secret = "...";
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
Provider = new MyAuthProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new [] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider("MyApp", TextEncodings.Base64Url.Decode(key))
}
});
// All the other stuff here
}
When you've implemented your auth like this, you can access the token information in your WebApi controller via the User.Identity property. To read custom claims, you can cast it to ClaimsIdentity.
var identity = User.Identity as ClaimsIdentity;
var myClaim = identity.Claims.FirstOrDefault(c => c.Type == "myClaimKey");
I've been trying to setup a new IdentityServer3 with AspNetIdentity for a few days now. I'm able to login using my existing Identity DB and that's all good but I can never get the User.Identity.Name to contain data.
I've tried multiple attempts at adding custom claims & scopes and adding scopes to clients.
Finally, I loaded up the IdentityServer3 Sample repository and tested it out with the webforms client project since it already used the User.Identity.Name in it's About page.
Using WebForms sample client + AspNetIdentity sample server = User.Identity.Name is always null
Using WebForms sample client + SelfHost with Seq sample server = User.Identity.Name with data
I've tried other sample host projects that all populate the User.Identity.Name value just fine.
Now, on the client side I've written a workaround to pull the 'preferred_username' claim value and set the 'name' claim with it.
var id = new claimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
//set the User.Identity.Name value
var name = id.Claims.Where(x => x.Type == "name").Select(x => x.Value).FirstOrDefault() ??
id.Claims.Where(x => x.Type == "preferred_username").Select(x => x.Value).FirstOrDefault();
id.AddClaim(new Claim("name", name));
My questions are:
Why doesn't the AspNetIdentity package fill this by default?
And what do I need to change on the server side so that I don't need to change the client?
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "My Admin API")
{
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }
}
};
}
In Identityserver4 you can add the UserClaims to your resource. Fixed it for me.
On IdentityServer4 you can implement IProfileService on server and add the Claim in GetProfileDataAsync
public class AspNetIdentityProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
context.IssuedClaims.AddRange(claims);
//Return
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = (user != null) && ((!user.LockoutEnd.HasValue) || (user.LockoutEnd.Value <= DateTime.Now));
//Return
return Task.FromResult(0);
}
}
Then add "AddProfileService()" to your ConfigureServices method.
services.AddIdentityServer(...)
...
.AddProfileService<AspNetIdentityProfileService>();
I've been trying to understand how the reset password & account confirmation works in ASP.NET Identity. I'd just like to know if the Tokens are being stored and if so, where?
The links I receive when I'm using the password reset feature look something like this
http://localhost:1470/Account/ResetPassword?userId=a8b1389c-df93-4dfc-b463-541507c1a4bc&code=yhUegXIM9SZBpPVbBtv22kg7NO7F96B8MJi9MryAadUY5XYjz8srVkS5UL8Lx%2BLPYTU6a6jhqOrzMUkkMyPbEHPY3Ul6%2B%2F0s0qQvtM%2FLLII3s29FgkcK0OnjX46Bmj9JlFCUx53rOH%2FXMacwnKDzoJ1rbrUyypZiJXloIE50Q6iPuMTUHbX9O%2B3JMZtCVXjhhsHLkTOn9IVoN6uVAOMWNQ%3D%3D
My guess is that the tokens are stored in the link itself since I cannot find any trace of it anywhere else. Maybe someone knows for sure?
As I mentioned in the comment
"Tokens are generated using the SecurityStamp and validating against the SecurityStamp and not storing anywhere in database or local file storage. If you update the SecurityStamp, then previous tokens are no longer valid."
#DSR is correct but I would like to add some information to this as well.
If you have set up a Web project with Individual User Accounts go to:
App_Start -> IdentityConfig.cs
There you will see code like this:
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
The description for DataProtectorTokenProvider<TUser, TKey> gives the information:
Represents a token provider that uses an IDataProtector to generate
encrypted tokens based off of the security stamp.
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn613280(v%3dvs.108)
We can however try to dig a bit deeper how it really works. The token verification will fail if different Application Pool Identities are used for creating and validating a token on a single server. This points to that the actual protection mechanism would look something like this:
System.Security.Cryptography.ProtectedData.Protect(userData, entropy, DataProtectionScope.CurrentUser);
Given that it works if all sites use the same Application Pool Identity points to this as well. Could also be DataProtectionProvider with protectionDescriptor "LOCAL=user". It should have worked with different Application Pool Identities if LOCAL=machine was set.
new DataProtectionProvider("LOCAL=user")
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotector?view=netframework-4.7.2
https://learn.microsoft.com/en-us/uwp/api/windows.security.cryptography.dataprotection.dataprotectionprovider
dataProtectionProvider is of type IDataProtectionProvider.
It is injected in Startup.Auth.cs like this:
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
CreatePerOwinContext is located in the assembly Microsoft.AspNet.Identity.Owin -> AppBuilderExtensions.cs. Both ASP.NET Identity and ASP.NET Core Identity are open source and can be viewed at GitHub.
public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app,
Func<IdentityFactoryOptions<T>, IOwinContext, T> createCallback,
Action<IdentityFactoryOptions<T>, T> disposeCallback) where T : class, IDisposable
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (createCallback == null)
{
throw new ArgumentNullException("createCallback");
}
if (disposeCallback == null)
{
throw new ArgumentNullException("disposeCallback");
}
app.Use(typeof (IdentityFactoryMiddleware<T, IdentityFactoryOptions<T>>),
new IdentityFactoryOptions<T>
{
DataProtectionProvider = app.GetDataProtectionProvider(),
Provider = new IdentityFactoryProvider<T>
{
OnCreate = createCallback,
OnDispose = disposeCallback
}
});
return app;
}
https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
https://archive.codeplex.com/?p=aspnetidentity#src/Microsoft.AspNet.Identity.Owin/Extensions/AppBuilderExtensions.cs
app.GetDataProtectionProvider() is in turn located in assembly Microsoft.Owin.Security that is also Open Source.
public static IDataProtectionProvider GetDataProtectionProvider(this IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
object value;
if (app.Properties.TryGetValue("security.DataProtectionProvider", out value))
{
var del = value as DataProtectionProviderDelegate;
if (del != null)
{
return new CallDataProtectionProvider(del);
}
}
return null;
}
https://github.com/aspnet/AspNetKatana/blob/release/src/Microsoft.Owin.Security/DataProtection/AppBuilderExtensions.cs
We can also see that CreateDataProtector has a fallback to the implementation DpapiDataProtectionProvider.
private static IDataProtectionProvider FallbackDataProtectionProvider(IAppBuilder app)
{
return new DpapiDataProtectionProvider(GetAppName(app));
}
When reading about DpapiDataProtectionProvider(DPAPI stands for Data Protection Application Programming Interface) the description says:
Used to provide the data protection services that are derived from the
Data Protection API. It is the best choice of data protection when you
application is not hosted by ASP.NET and all processes are running as
the same domain identity.
The Create method purposes are described as:
Additional entropy used to ensure protected data may only be
unprotected for the correct purposes.
The protector class itself then looks like this:
using System.Security.Cryptography;
namespace Microsoft.Owin.Security.DataProtection
{
internal class DpapiDataProtector : IDataProtector
{
private readonly System.Security.Cryptography.DpapiDataProtector _protector;
public DpapiDataProtector(string appName, string[] purposes)
{
_protector = new System.Security.Cryptography.DpapiDataProtector(appName, "Microsoft.Owin.Security.IDataProtector", purposes)
{
Scope = DataProtectionScope.CurrentUser
};
}
public byte[] Protect(byte[] userData)
{
return _protector.Protect(userData);
}
public byte[] Unprotect(byte[] protectedData)
{
return _protector.Unprotect(protectedData);
}
}
}
https://learn.microsoft.com/en-us/previous-versions/aspnet/dn253784(v%3dvs.113)