ASP.NET Core 6 non Default Identity UI based Authentication with email/sms based TFA - asp.net-core-identity

I have to port a website which has a TFA authentication via sms/email to asp.net 6. The client doesn’t want to use an authenticator app. The application has some specialities such as the language of the user in the URL path as the first component.
In this post I searched a way to introduce the language part and was, although in a very ugly manner, successful.
However I'm also quite unhappy to use the default UI in general for this application, since I have to manually deactivate all the things that are not required but are included in the library. It feels extremely untidy to have such a huge framework of pages active for so little of required functionality. The only things I need are
Login with a username and password
Optionally checking a tfa email/sms code if the user has configured its account for this.
The configuration of the user's profile is done in another application and should not be included in this application (hence all the default identity ui stuff has to be deactivated).
I tried to create the authentication logic manually via controller actions and not installing the default ui at all. For the login with username and password, this was fairly easy.
However, setting up the TFA part seems quite tedious and dangerous (from a security perspective). I have not found any documentation what resources have to be registered and how to setup the authentication system.
Is searching out all the required dependencies and creating the code from the Microsoft source code of default identity UI the only way?
Or is there a template solution to accomplish the desired goal?

Since there seems no such information available, I gathered the necessary code from the ms source code. Maybe it helps someone:
Registration
Copied out of the ms source code, here the required registrations:
builder.Services.AddIdentityCore<MyUserType>().AddDefaultTokenProviders();
builder.Services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<MyUserType>>();
builder.Services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<MyUserType>>();
builder.Services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
// options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = [your login path];
o.LogoutPath = [your logout path];
})
//.AddCookie(IdentityConstants.ExternalScheme, o =>
//{
// o.Cookie.Name = IdentityConstants.ExternalScheme;
// o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
//})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
The registration for the external authentication is commented out, maybe someone wants to use that also, therefore i have not directly removed it from the code. The login path can be set accordingly to your login-action. See my other post about how to extend the login url to introduce language and other path components.
Controller-code
The controller's action code and views can then derived from the original default identity ui code. The AddDefaultTokenProviders call is required to have the email token provider registered.
Result
In this way, the default ui and all its abundant functionality is no more in the project and one can selectively decide, what parts to integrate.

Related

how to use services before app build in .net core 6.0

I have earlier achieved this .net 3.1. But it couldn't be possible with .Net 6 because of startup.cs removed.
I have registered a few services,
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var appSettings = builder.Configuration.GetSection("AppSettings").Get<AppSettings>();
builder.Services.AddScoped<IEncryption, Encryption>();
//Here I need to get the IEncryption Service, and call the method in this service to encrypt/decrypt the connection string to pass to DBContext Service.
builder.Services.AddDbContext<CatalogDbContext>(options => options.UseNpgsql(
appSettings.ConnectionString));
var app = builder.Build();
Earlier in .NET 3.1, I used BuildServicProvider() to get the Encryption service, and call the methods in that service to do the required logic then got the proper connection string I wanted that would be passed to the DBContext service on the next line.
Now, .NET 6/7 is forced to use the services only after app = builder.Build(); so, I can't register the DBCOntext after the build() method.
How can I solve this case? Any recommended approach to do this in .NET 6/7?
You still can useStartup.cs in .net 6
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services); // calling ConfigureServices method
var app = builder.Build();
startup.Configure(app, builder.Environment); // calling Configure method
And then you can use ConfigureServices and Configure methods to register your services before building.
You didn't need to use BuildServiceProvider in .NET Core 3.1 either. AddDbContext has an overload that provides access to an IServiceProvider instance :
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var myOwnDecrypter=services.GetRequiredService<IMyOwnDecrypter>();
var cns=myOwnDecrypter.Decrypt(appSettings.ConnectionString,key);
options.UseNpgsql(cns);
});
or, if you use the ASP.NET Core Data Protection package :
builder.Services.AddDataProtection();
...
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var protector = services.GetDataProtector("Contoso.Example.v2");
var cns=protector.Unprotect(appSettings.ConnectionString);
options.UseNpgsql(cns);
});
or, if IConfiguration.GetConnectionString is used :
builder.Services.AddDataProtection();
...
builder.Services.AddDbContext<CatalogDbContext>((services,options) =>{
var conn_string=services.GetService<IConfiguration>()
.GetConnectionString("MyConnectionString");
var protector = services.GetDataProtector("Contoso.Example.v2");
var cns=protector.Unprotect(conn_string);
options.UseNpgsql(cns);
});
That said, it's the configuration provider's job to decrypt encrypted settings, not the service/context's. ASP.NET Core's configuration allows using multiple different configuration sources in the same host, not just a single settings file. There's nothing special about appsettings.json. That's just the default settings file name.
You can add another settings file with sensitive contents with AddJsonSettings. That file could use the file system's encryption, eg NTFS Encryption, to ensure it's only readable by the web app account
You can read settings from a key management service, like Hashicorp, Azure Key Vault, Amazon Key Management etc.
You can create your own provider that decrypts its input. The answers to this SO questino show how to do this and one of them inherits from JsonConfigurationProvider directly.
Important Caveat: In general, my suggestion below is a bad practice
Do not call BuildServiceProvider
Why is bad? Calling BuildServiceProvider from application code results in more than one copy of singleton services being created which might result in incorrect application behavior.
Justification: I think it is safe to call BuildServiceProvider as long as you haven't registered any singletons before calling it. Admittedly not ideal, but it should work.
You can still callBuildServiceProvider() in .Net6:
builder.Services.AddScoped<IEncryption, Encryption>();
// create service provider
var provider = builder.Services.BuildServiceProvider();
var encryption = scope.ServiceProvider.GetService<IEncryptionService>();
// use service here
or alternatively
builder.Services.AddScoped<IEncryption, Encryption>();
var provider = builder.Services.BuildServiceProvider();
using (var scope = provider.CreateScope()) {
var encryption = scope.ServiceProvider.GetService<IEncryptionService>();
// use service here
}
Alternative:
You can still use the classic startup structure in .Net6/7. We upgraded our .Net3.1 projects to .Net6 without having to rewrite/restructure the Startup()

Google Drive SDK 1.8.1 RedirectURL

Is there any way to provide RedirectURL then using GoogleWebAuthorizationBroker?
Here is the sample code in C#:
Task<UserCredential> credential = GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, scopes, GoogleDataStore.User, cancellationToken, dataStore);
Or we have to use different approach?
I have an "installed application" that runs on a user's desktop, not a website. By default, when I create an "installed application" project in the API console, the redirect URI seems to be set to local host by default.
What ends up happening is that after the authentication sequence the user gets redirected to localhost and receives a browser error. I would like to prevent this from happening by providing my own redirect URI: urn:ietf:wg:oauth:2.0:oob:auto
This seems to be possible using Python version of the Google Client API, but I find it difficult to find any reference to this with .NET.
Take a look in the implementation of PromptCodeReceiver, as you can see it contains the redirect uri.
You can implement your own ICodeReceiver with your prefer redirect uri, and call it from a WebBroker which should be similar to GoogleWebAuthorizationBroker.
I think it would be great to understand why can't you just use PrompotCodeReceiver or LocalServerCodeReceiver.
And be aware that we just released a new library last week, so you should update it to 1.9.0.
UPDATE (more details, Nov 25th 2014):
You can create your own ICodeReceiver. You will have to do the following:
* The code was never tested... sorry.
public class MyNewCodeReceiver : ICodeReceiver
{
public string RedirectUri
{
get { return YOU_REDIRECT_URI; }
}
public Task<AuthorizationCodeResponseUrl> ReceiveCodeAsync(
AuthorizationCodeRequestUrl url,
CancellationToken taskCancellationToken)
{
// YOUR CODE HERE FOR RECEIVING CODE FROM THE URL.
// TAKE A LOOK AT THE FOLLOWING:
// PromptCodeReceiver AND LocalServerCodeReceiver
// FOR EXAMPLES.
}
}
PromptCodeReceiver
and LocalServerCodeReceiver.
Then you will have to do the following
(instead of using the GoogleWebAuthorizationBroker.AuthorizeAsync method):
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = secrets,
Scopes = scopes,
DataStore = new FileDataStore("Google.Apis.Auth");
};
await new AuthorizationCodeInstalledApp(
new GoogleAuthorizationCodeFlow(initializer),
new MyNewCodeReceiver())
.AuthorizeAsync(user, taskCancellationToken);
In addition:
I'll be happy to understand further why you need to set a different redirect uri, so we will be able to improve the library accordingly.
When I create an installed application the current PromptCodeReceiver and LocalServerCodeReceiver work for me, so I'm not sure what's the problem with your code.

I want my Domino Servlet to get an authenticated user session

It seems a like a pretty fundamental question, in a running Servlet hosted on Domino I want to access Domino resources that I have wisely protected using the the very fine security of IBM Notes and Domino.
I want the Servlet to be able to read and write data to Domino whilst keeping that data from the client that called the Servlet (or xAgent) and preventing the client from writing directly.
I'd be happy to be able to get a session that represented the signer of the application. I can get a session for a registered user by calling the Servlet using ?open&login and signing in. That's not practical.
I've looked here: How can you use SessionAsSigner in a Java Bean called from an XPage? where Mark Leusink (https://stackoverflow.com/users/1177870/mark-leusink) implies the use of ExtLib's getCurrentSessionAsSigner() could be used. I've tried it, having signed the whole application with a single user id and it doesn't return a session. The answer seems to lie in the Servlet's inability to get a FacesContext object.
This feels like the answer should be obvious but it isn't to me. Any ideas?
FacesContext is JSF stuff and can be used from XAgent (=XPage).
In a servlet you can do this:
Session session = NotesFactory.createSession(null, "user", "password");
Server ID usually has no password and doing this will use the server ID:
Session session = NotesFactory.createSession();
Check the source of the WebDav project on OpenNTF. It has all the code you need
There have been lots of good answers to the original question. Thanks very much.
The solution I propose to use is to port the code I have to OSGi plugins. It appears that java code/Servlets within the NSF context are subject to security controls that are relaxed when the same code runs within the OSGi context. The code:
try {
NotesThread.sinitThread();
Session s = NotesFactory.createSession("","<my username>","<my password>");
.....
session = null;
} catch (Exception e) {
} finally {
NotesThread.stermThread();
}
Runs fine in the OSGI context, but within in an NSF produc
com.ibm.domino.osgi.core.context.ContextInfo.getUserSession()
Jason - I assume you basically want the same functionality you would get running a Web Query Save agent if you didn't select run as Web User selected, in other words as the signer of the code.
You could try setting up a internet site rule to allow basic authentication for the specific application path you wanted to use - might be worth using a subdomain for this.
Then within the Servlet call this URL, whilst setting the Basic authorization parameters (username & password).
Something like this.
URL url = new URL(URL_TO_CALL);
String authStr = "USERNAME:PASSWORD";
String authEncoded = Base64.encodeBytes(authStr.getBytes());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoOutput(true);
connection.setRequestProperty("Authorization", "Basic " + authEncoded);
InputStream is = connection.getInputStream();

Front-end Ajax in ModX Revolution

What's the proper way for implementing front-end Ajax functionality in ModX Revolution? I like the idea of connectors and processors, but for some reason they are for back-end use only - modConnectorResponse checks if user is logged in and returns 'access denied', if he is not.
Inserting a snippet into resource and calling it by resource URL seems a one-time solution, but that doesn't look right to me.
So how do I get safe Connector-like functionality for front-end?
So, as boundaryfunctions said, it's not possible and ModX developers recommend using a resource with a single snippet included. But for those who despite the will of developers look for Connector-like functionality, there may be a solution made by guess who-- ModX core developer splittingred in Gallery extra. In connector.php, before handleRequest() call, there's a code that fakes authorisation:
if ($_REQUEST['action'] == 'web/phpthumb') {
$version = $modx->getVersionData();
if (version_compare($version['full_version'],'2.1.1-pl') >= 0) {
if ($modx->user->hasSessionContext($modx->context->get('key'))) {
$_SERVER['HTTP_MODAUTH'] = $_SESSION["modx.{$modx->context->get('key')}.user.token"];
} else {
$_SESSION["modx.{$modx->context->get('key')}.user.token"] = 0;
$_SERVER['HTTP_MODAUTH'] = 0;
}
} else {
$_SERVER['HTTP_MODAUTH'] = $modx->site_id;
}
$_REQUEST['HTTP_MODAUTH'] = $_SERVER['HTTP_MODAUTH'];
}
Works for me. Just need to replace first if condition with my own actions.
UPDATE: I forgot to mention that you need to pass &ctx=web parameter with your AJAX request, because default context for connectors is "mgr" and anonymous users will not pass policy check (unless you set access to the "mgr" context for anonymous users).
And also the code from Gallery extra I posted here seems to check some session stuff that for me doesn't work with anonymous front-end users (and works only when I'm logged in to back-end), so I replaced it with the next:
if (in_array($_REQUEST['action'], array('loadMap', 'loadMarkers'))){
$_SESSION["modx.{$modx->context->get('key')}.user.token"] = 1;
$_SERVER['HTTP_MODAUTH'] = $_REQUEST['HTTP_MODAUTH'] = 1;
}
I don't know if this code is 100% safe, but when anonymous user calls it, he doesn't appear to be logged in to Manager, and when admin is logged in and calls the action from back-end, he is not logged off by force. And that looks like enough security for me.
This solution is still portable (i.e. can be embedded into distributable Extra), but security should be researched more seriously for serious projects.
As far as I know, this is not possible in modX at the moment. It has already been discussed on the modx forums and filed as a bug here, but it doesn't look like anybody is working on it.
There are also two possible workarounds in the second link. Personally, I would favour putting the connector functionality into the assets folder to keep the resource tree clean.
There's a more complete explanation of the technique used in Gallery here:
http://www.virtudraft.com/blog/ajaxs-connector-file-using-modxs-main-index.php.html
It allows you to create a connector to run your own processors or a built-in MODX processors without creating a resource.

MVC3 + WIF - FederationResult missing "wctx"

I have an MVC3 app for which I want to implement claims support. My goal is as follows:
provide a SignIn link, which when clicked displays a popup window with username/password and Facebook/WindowsLive/Google etc. links
automatically redirect to my SignIn page when a protected controller is accessed e.g. /Order/Delete
I've set up the application and providers in AppFabricLabs.com and included the STS in my project. I've also created an implementation of IAuthorizationFilter so I can mark my controllers as [WifAuth] and successfully get the OnAuthorization method called. I've implemented the use-case where the visitor has not been authenticated like this:
private static void AuthenticateUser(AuthorizationContext context)
{
var fam = FederatedAuthentication.WSFederationAuthenticationModule;
var signIn = new SignInRequestMessage(new Uri(fam.Issuer), fam.Realm);
context.Result = new RedirectResult(signIn.WriteQueryString());
}
and successfully get AppFabricLabs page with my Identity Provider choices (haven't figured out how to customise that page). When I log in my returnUrl gets called so I land in a controller method /Home/FederationResult, however the form posted to me contains only wa and wresult fields but I need wctx to know where to send the user... I haven't been able to figure out why.
the wresult is an XML document that contains (amongst a bzillion other things) the name and e-mail address of the user logging in but sadly does not contain the url to which the user was headed.
have I failed to configure something or am I just off base? thoughts anyone?
e
Just specify a Context for the SignInRequestMessage:
signIn.Context = HttpContext.Current.Request.RawUrl;
The wctx parameter is included in every request/response and also part of the form posted finally to your site.

Resources