Open touch\face ID\biometric auth in blazor wasm app - authentication

I need to add the biometric authentication to my blazor wasm app.
I've found some hard to implement\understand libraries that uses a third part authentication server side.
I only need to open the biometric's dialog from the device (iOS, Android, desktop browser..etc..) then I would to manage manually the authentication with my actual method.
It is possible? If yes, how?
Thanks! :)

Haven't found anything built into blazor yet, so it seems for now you'll have to use jsinterop. Haven't actually tried it in Blazor, but I'd expect it to happen mostly in the js code anyway. There's a nice website just for this, since you have to add a few options and figure out asymmetric keys: https://webauthn.guide/
Might try it soon to find out if there's a more compressed way of presenting an MCVE - if I do figure it out I will update this.
Update
Tried it, this opens the dialog for me.
The actual call happens in JS:
async function doIt(options) {
var newCreds = await navigator.credentials.create({ publicKey: options });
console.log("Created new credentials:", newCreds);
return newCreds;
}
window.doIt = doit;
Put a button into my razor:
<button #onclick="DoIt">Do it</button>
Then the corresponding method and types in the #code block:
private async void DoIt()
{
var credOptions = new PublicKeyCredentialCreationOptions();
Console.WriteLine("Sending options for " + credOptions.user.displayName);
var cred = await Js.InvokeAsync<PublicKeyCredential>("doIt", credOptions);
Console.WriteLine("Received cred");
Console.WriteLine(cred);
}
// Do all the things (I think this is like pattern/faceID/touchID/etc)
static readonly int[] Algs = { -7, -8, -35, -36, -37, -38, -39, -257, -258, -259, -65535 };
private static PublicKeyCredentialCreationOptions.PublicKeyCredentialParameters Alg(int alg)
{
return new PublicKeyCredentialCreationOptions.PublicKeyCredentialParameters("public-key", alg);
}
public class PublicKeyCredentialCreationOptions
{
public byte[] challenge { get; set; } = DateTime.Now.ToString().Select(c => (byte)c).ToArray(); // Just random stuff here I think?
public RelyingParty rp { get; set; } = new ("WebAuthnExample"); // If I understand correctly, id will be auto filled with the current domain
public User user { get; set; } =
new("metallkiller".Select(c => (byte)c).ToArray(), "metallkiller", "Metallkiller");
public PublicKeyCredentialParameters[] pubKeyCredParams { get; set; } = Algs.Select(Alg).ToArray();
public long timeout { get; set; } = 60000; // Not entirely sure what this does - docs has more info
public string attestation { get; set; } = "direct"; // No idea I just copied this
public record RelyingParty(string name);
public record User(byte[] id, string name, string displayName);
public record PublicKeyCredentialParameters(string type, int alg);
}
Good news: This opens the biometric auth dialog and creates a credential.
Bad news: Can't get the credential back to dotnet, maybe I forgot to do some jsinterop-magic or it just doesn't work with credentials, then we might have to read all the things in JS and return them in our own object or something. I'd appreciate anyone telling me what's going on here.
Edit: Source for the return types: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e57ff15825e1a05c923f80f39dbb7966d20db950/types/webappsec-credential-management/index.d.ts

Related

The best way to save the String Variable after being redirected Page in ASP.NET CORE

How can I save the form values as a string after the page is redirected?
I use a static variable, if the number of users on the site increases for example 20000, will the site have problems?
Which method is better?
Using a static variable?
session?
Cookies?
ViewBag?
View State?
sql server database?
Actually, There is no the best method, Only the most suitable method. The methods you list above all have their own most suitable use cases.
From your comment, I think you may wanna redirect to another page with a string parameter, So you can try to use :
return RedirectToAction("actionName","controllerName",new { Name="xxx"});
It will send a get request: acontrollerName/actionName?Name=xxx.
Or, if you wanna save that string value for a long time, You can also try to use Session or Database. But one thing you need to consider is that you need to set the session time out according to the actual situation of your project.
==========================edited===========================
I think you can use Session to achieve it.
public class TestModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public async Task<IActionResult> OnPostSelectMenu(int selectedid)
{
TestModel selected = await _db.Table.FindAsync(selectedid);
//set session
HttpContext.Session.SetString("Key", JsonConvert.SerializeObject(selected));
return RedirectToPage("MyPage");
}
public async Task<IActionResult> OnGetAsync()
{
//......
//get session
Input = JsonConvert.DeserializeObject<TestModel>(HttpContext.Session.GetString("Key"));
return Page();
}

Google reCAPTCHA Enterprise with ASP.NET CORE 3.1

After some hours spent searching the web for implementation of Google reCAPTCHA Enterprise with ASP.NET CORE 3.1, I must, unfortunately, admit that I was not able to find anything I could use in my project.
I've read the docs following the official site, but in the end, I'm still stucking for a clean implementation.
In ASP.NET Monsters there is an example, but targeting reCAPTCHA V3 and not reCAPTCHA enterprise.
There is also a nice post here Google ReCaptcha v3 server-side validation using ASP.NET Core 5.0, but again on reCAPTCHA V3.
Any help is appreciated.
So for me i needed to implement google recapthca with dotnet 5 using an angular front end. I am sure you can replace the angular front end with the native javascript instead, but this took me hours of investigating so hopefully it will help people.
First i had to enable reCAPTCHA Enterprise, to do this i went to https://cloud.google.com/recaptcha-enterprise/ and then clicked on the "go to console" button. This took me to my Google Cloud Platform. From here i needed to create a key, fill in the options and save. This key will be referred to as your SITE_KEY.
-- IF YOU ARE USING ANGULAR, READ THIS, ELSE SKIP THIS STEP AND IMPLEMENT IT YOURSELF
On the client i used ng-recaptcha, you can find it here
To implement this component, i added this import to my app.module.ts
import { RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha';
and this to the providers section
{
provide: RECAPTCHA_V3_SITE_KEY,
useValue: SITE_KEY_GOES_HERE
}
On my component, when the submit button is pressed, i used the ReCaptchaV3Service from the library above. My code looks like this
this.recaptchaV3Service.execute(YOUR_ACTION_NAME).subscribe((recaptchaResponse) => {
// now call your api on the server and make sure you pass the recaptchaResponse string to your method
});
The text YOUR_ACTION_NAME is the name of the action you are doing. In my case i passed 'forgotPassword' as this parameter.
-- END OF ANGULAR PART
Now on the server, first i included this into my project
<PackageReference Include="Google.Cloud.RecaptchaEnterprise.V1" Version="1.2.0" />
Once included in my service, i found it easier to create a service in my code, which is then injected. I also created a basic options class, which is injected into my service, it can be injected into other places if needed.
RecaptchaOptions.cs
public class RecaptchaOptions
{
public string Type { get; set; }
public string ProjectId { get; set; }
public string PrivateKeyId { get; set; }
public string PrivateKey { get; set; }
public string ClientEmail { get; set; }
public string ClientId { get; set; }
public string SiteKey { get { return YOUR_SITE_KEY; } }
/// <summary>
/// 0.1 is worst (probably a bot), 0.9 is best (probably human)
/// </summary>
public float ExceptedScore { get { return (float)0.7; } }
}
Some of these values are not used, but i have added them for the future, encase i do use them.
Then i have created my service, which looks like so (i have created an interface for injecting and testing)
IRecaptchaService.cs
public interface IRecaptchaService
{
Task<bool> VerifyAsync(string recaptchaResponse, string expectedAction);
}
RecaptchaService.cs
public class RecaptchaService : IRecaptchaService
{
#region IRecaptchaService
/// <summary>
/// Check our recaptcha
/// </summary>
/// <param name="recaptchaResponse">The response from the client</param>
/// <param name="expectedAction">The action that we are expecting</param>
/// <returns></returns>
public async Task<bool> VerifyAsync(string recaptchaResponse, string expectedAction)
{
// initialize request argument(s)
var createAssessmentRequest = new CreateAssessmentRequest
{
ParentAsProjectName = ProjectName.FromProject(_recaptchaOptions.ProjectId),
Assessment = new Assessment()
{
Event = new Event()
{
SiteKey = _recaptchaOptions.SiteKey,
Token = recaptchaResponse
}
},
};
// client
var cancellationToken = new CancellationToken();
var client = RecaptchaEnterpriseServiceClient.Create();
// Make the request
try
{
var response = await client.CreateAssessmentAsync(createAssessmentRequest, cancellationToken);
return response.TokenProperties.Valid && response.TokenProperties.Action.Equals(expectedAction) && response.RiskAnalysis?.Score >= _recaptchaOptions.ExceptedScore;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
}
}
#endregion
private RecaptchaOptions _recaptchaOptions;
public RecaptchaService(RecaptchaOptions recaptchaOptions)
{
_recaptchaOptions = recaptchaOptions;
}
}
Now my api endpoint, i inject this service and call it. Here is an example API method that calls the recaptchaService.
public async Task<IActionResult> ForgotPasswordAsync([FromBody] ForgotPasswordModel model)
{
// check our recaptchaResponse
var verified = await _recaptchaService.VerifyAsync(model.RecaptchaResponse, "forgotPassword");
if (!verified)
throw new ApplicationException("Recaptcha failed, please try again");
// successful, carry on
}
Hope this helps everyone, if there are any questions, please ask and i will edit this and update it with anything i have missed.

ASP.NET CORE Add easily accessible properties to logged user

In our Asp.Net Core (2.2) MVC project we had to use an existing database (including all user & role related tables) from our previous Asp.Net Web app project.
Retrieving user data in asp.net web app (and having it available throughout the website) was preatty simple: upon login fill a custom user class/object with all the properties you need, save it as a Session variable and you call it wherever you need it (without going to the database).
This seems to me a lot harder to achieve in Asp.Net Core. What I have so far is:
ApplicationUser class:
public class ApplicationUser : IIdentity
{
public int Id { get; set; }
public Uporabnik Uporabnik { get; set; }
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
public string Name { get; set; }
}
Login form:
public IActionResult Prijava(PrijavaModel model)
{
// check user credentials
//
// ... validation code here ...
//
if (uporabnik != null)
{
//Create the identity for the user
var identity = new ClaimsIdentity(new[] {
new Claim("Email", model.Email),
new Claim("Id", uporabnik.IdWebUser.ToString()),
new Claim("Name", uporabnik.ImeInPriimek),
new Claim(ClaimTypes.Name, uporabnik.ImeInPriimek),
new Claim(ClaimTypes.PrimarySid, uporabnik.IdWebUser.ToString())
}, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var login = HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return RedirectToAction("Index", "Home");
}
return View();
}
Now to retrieve the data in a controller I have to do something like this:
// id
int idUser = int.Parse(#User.Claims.FirstOrDefault(x => x.Type == "Id").Value);
// or email
string email = #User.Claims.FirstOrDefault(x => x.Type == "Email").Value;
Well, this all works fine, but it's definitely not practical. To access any other user data I can go to the database (by "ID") and retrieve it, but I don't think this is the right way to do it!?!
Can I expand the identity class in such a way that I can set the extra properties I need at login time and retrive in a fashion similar to something like this:
var property1 = #User.Property1;
var property2 = #User.Property2;
// or
var property1 = #User.MyExtraProperties.Property1;
var property2 = #User.MyExtraProperties.Property2;
Is it possible (and also keeping it simple)?
EDIT: since there are no answers/suggestions, can I do the same thing with a different approach?
Look like you only want to call your properties in a better way?
public class ApplicationUser : IdentityUser
{
public string CustomName { get; set; }
}
Assuming you have done adding your extra properties, you could create an extension method for your properties, so you can later call them like User.Identity.GetCustomName().
namespace Project.Extensions
{
public static class IdentityExtensions
{
public static string GetCustomName(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("CustomName");
return (claim != null) ? claim.Value : string.Empty;
}
}
}
Note that I didn't include the part where you add the claims, because you already have it. In this case, you should have CustomName claim.
Also, #Dementic is right about the session. If a user is removed/disabled, he would still have access to. So, having a db call each time you need to fetch information is correct.

Gathering Active Directory Data for MVC intranet application

I need assistance with gathering Active Directory data based on a table in my DB. I have an entity class that holds user requests. Each request has the user's windows name from System.Web.HttpContext.Current.User.Identity.Name. My problem is I cannot figure out how to setup a linq query to associate the AD username to the rest of the AD so I can display their full names instead of their username in my table. Here is what I have so far, any help will be appreciated.
public partial class RequestInfo
{
public int RequestInfoId { get; set; }
public string RequestByUserADId { get; set; }
public System.DateTime RequestDateTime { get; set; }
public string Explanation { get; set; }
public virtual UserInfo UserInfo { get; set; } // where I define my custom roles
}
I can query AD by using the code below. I have tried Get Active Directory User Information With Windows Authentication in MVC 4, but it did not help.
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, requestByAdId))
{
return user.DisplayName
}
I may be off here because I am not sure if you are able to successful establish a user principal or not but if you have the user principal you can get property information like the following:
user.GetProperty("propertyName")
Here is a static method that should get you the department for a user, for example.
public static String GetDepartment(UserPrincipal principal)
{
return principal.GetProperty("department");
}
Let me know where this gets you and I can elaborate further if this isn't working.
Edit
It appears you need to go one level deeper to get the fields that aren't by default a part of the user principal. For this you will need to get the directory entry from the user principal first:
DirectoryEntry directoryEntry = (userPrincipal.GetUnderlyingObject() as DirectoryEntry);
Then you need to check if the attribute you are looking for exists, and if it does, get the value. A great way to do this is to create a helper method that you pass your directory entry to along with the string value for the property name that you want to get.
public string GetProperty(DirectoryEntry directoryEntry, string propertyName)
{
if (directoryEntry.Properties.Contains(propertyName))
{
return directoryEntry.Properties[propertyName][0].ToString();
}
else
{
return string.Empty;
}
}
Please note that going to the underlying object is expensive. I believe this operation, by default, is cached for you so subsequent use of this information can be retrieved from cache. Playing around with
directoryEntry.RefreshCache
will get you started with that.
Let me know if this does the trick for you!

Redis caching with ServiceStack OrmLite and SQL Server persistence

We have a Web app (ASP.NET/C#) with SQL Server backend. We use ServiceStack OrmLite as our POCO Micro ORM. We would now like to extend a part of our app to cache frequently-read data (mainly a collection of POCO objects as values, with numeric keys). But I'm not sure how to go about integrating a simple caching solution (in-memory or Redis based) that works seamlessly with OrmLite and MSSQL as the Master database.
I've read about the ServiceStack Redis Client, MemoryCacheClient and Multi nested database connections (OrmLiteConnectionFactory), but I couldn't find any examples, tutorial or code samples to learn more about implementing caching that works with OrmLite.
Any suggestions or links will be helpful and much appreciated.
I use this extension to help simplify the integration between the db and the cache.
public static class ICacheClientExtensions
{
public static T ToResultUsingCache<T>(this ICacheClient cache, string cacheKey, Func<T> fn, int hours = 1) where T : class
{
var cacheResult = cache.Get<T>(cacheKey);
if (cacheResult != null)
{
return cacheResult;
}
var result = fn();
if (result == null) return null;
cache.Set(cacheKey, result, TimeSpan.FromHours(hours));
return result;
}
}
public class MyService : Service
{
public Data Get(GetData request)
{
var key = UrnId.Create<Data>(request.Id);
Func<Data> fn = () => Db.GetData(request.Id);
return Cache.ToResultUsingCache(key, fn);
}
[Route("/data/{id}")]
public class GetData: IReturn<Data>
{
public int Id{ get; set; }
}
}
You'd need to implement the caching logic yourself, but it's not much work - here's a pseudocode example:
public class QueryObject
{
public DateTime? StartDate { get; set; }
public string SomeString { get; set; }
}
public class Foo
{
public DateTime DateTime { get; set; }
public string Name { get; set; }
}
public class FooResponse
{
public List<Dto> Data { get; set; }
}
public FooResponse GetFooData(QueryObject queryObject)
{
using (var dbConn = connectionFactory.OpenDbConnection())
using (var cache = redisClientsManager.GetCacheClient())
{
var cacheKey = string.Format("fooQuery:{0}", queryObject.GetHashCode()); //insert your own logic for generating a cache key here
var response = cache.Get<Response>(cacheKey);
//return cached result
if (response != null) return response;
//not cached - hit the DB and cache the result
response = new FooResponse()
{
Data =
dbConn.Select<Foo>(
x => x.DateTime > queryObject.StartDate.Value && x.Name.StartsWith(queryObject.SomeString)).ToList()
};
cache.Add(cacheKey, response, DateTime.Now.AddMinutes(15)); //the next time we get the same query in the next 15 mins will return cached result
return response;
}
}
Have you checked Service stack caching wiki. It gives detailed info about caching. Now in your case from the details you are providing I can say that you can go for any kind of caching. As of now it will not make any difference.
PS: A piece of advice caching should be done when there is no option or the only thing pending in application. Because it comes with it's own problem is invalidating caching, managing and all that. So, if you application is not too big, just leave it for now.