Google reCAPTCHA Enterprise with ASP.NET CORE 3.1 - asp.net-core

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.

Related

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

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

HttpClient.GetAsync return HttpResponseMessage with null header

net 5.0 lover.
I am new in blazor and .net 5.0, I develop the application with blazor WebAssembly and WebApi.
There are two major Projects: Client, Server.
Client is Blazor WebAssembly and Server is WebApi Controller Project.
In server side, in controller, HttpGet Method, i add a value to Response header:
[HttpGet]
public async Task<ActionResult<IList<Country>>> GetAsync([FromQuery] Pagination paginationDto)
{
/...
httpContext.Response.Headers.Add("TotalPages", totalPages.ToString());
//...
IList<Country> = ...
return result;
}
In Client project razor page, call the api with following method from generic calss:
protected virtual async Task<PaginatedResponse<O>> GetAsync<O>(Pagination pagination)
{
HttpResponseMessage response = null;
try
{
response = await httpClient.GetAsync(RequestUri);
if (response.IsSuccessStatusCode)
{
try
{
//This response Header always is null!
System.Console.WriteLine("response.Headers: " + response.Headers.ToString());
O result = await response.Content.ReadFromJsonAsync<O>();
var paginatedResponse = new PaginatedResponse<O>
{
Response = result,
TotalPages = totalPages
};
return paginatedResponse;
}
//...
return default;
}
When Api call from postman the result and Header is fine and TotalPages is there.
In Client App, the result is ok, but the Header is null.
Any information will save me ;-)
Thanks in Advance.
I think you're overcomplicating this by trying to use headers to pass back a result that can be passed more easily as part of the content. You even sort of realise this you're trying to use a PaginatedResponse in the Blazor client.
So instead of the API returning just a list, have a PaginatedResponse class in a shared library somewhere.. e.g.
/// <summary>
/// Paged result class
/// </summary>
/// <typeparam name="T"></typeparam>
public class PaginatedResponse<T>
{
public int TotalPages { get; set; }
public int Page { get; set; }
public List<T> Data { get; set; }
}
Your API then returns this
[HttpGet]
public async Task<ActionResult<PaginatedResponse<Country>>> GetAsync([FromQuery] Pagination paginationDto)
{
// ... query results here
var result = new PaginatedResponse<Country>()
{
Page = x,
TotalPages = totalPages,
Data = countrylist // from query
};
return result;
}
Your Blazor client can then use the same PaginatedResponse class and just use the standard GetFromJsonAsync method:
var result = await Http.GetFromJsonAsync<PaginatedResponse<Country>>("yourApiUri");
This is why I love Blazor!
This is the exactly answer for how search for answer:
in Server project, in startup.cs, in ConfigureServices method, add following code for CORS or update your CORS rule:
services.AddCors(options => options.AddPolicy(name: "WebApiProjectName or somthing", builder =>
{
builder.WithOrigins("http://localhost:xxxx") //xxxxx is server port
.AllowAnyMethod()
.AllowAnyHeader()
//.AllowCredentials() // its optional for this answer
.WithExposedHeaders("*"); // this is the code you need!
}));

IdentityUser not serializing Web Api

Not sure what is going on here.
I am exposing the Identity functionality through a Web API project. The CRUD aspect will be exposed to an admin app and the login, registration in a public facing app.
Right now I am just trying to return a list of all users in the database through a Web Api controller action. I am getting nothing output to the response, but I do get back data from the service:
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("")]
public async Task<IHttpActionResult> GetAllUsers()
{
var model = await _userService.GetAllUsers(); //<---Gets List<AppUser> here?
return Ok(model);
}
This action shows nothing on fiddler or Postman?
Any ideas?
public class AppUser : IdentityUser
{
public DateTime Created { get; set; }
}
Is there something special about the IdentityUser class that prevents it from being serialized?
Here is the web api serialization config:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.Add(new JsonFormatter());
}
public class JsonFormatter : JsonMediaTypeFormatter
{
public JsonFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
this.SerializerSettings.Formatting = Formatting.Indented;
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
Found my answer. The IdentityUser class is not really meant to be exposed over an API; lots of sensitive data and all.
However this is will sit behind a firewall and I do not feel like writing a DTO and mapper just to make this work.
The answer is explained here
Basically you just need to override the properties you want exposed and decorate them with a DataMember attribute for serialization.

SQL Azure Federation with S#arp Architecture

I’m using S#harp Architecture, has anyone found a way to access SQL Azure Federations with it?
I am aware that the following command must be executed outside of the transaction since SQL Azure does not allow “use Federation” statements within a transaction.
use Federation CustomerFederation (CustomerID=2) with reset, filtering=on
GO
<some sql statement...>
GO
There is another post on here that shows an example with creating a custom NHibernate Session class, but how can this be accomplished/extended using S#arp Architecture?
I'm also aware that there are other sharding options to SQL Azure Federation such as NHibernate.Shards or a multi-tenant S#arp Architecture extension but, please, keep to answering the question as opposed to providing other options.
I know I’m not the only person using S#arp Architecture and SQL Azure Federations and Google hasn't provided much so if anyone else out their has found a solution then, please, share.
Since no one has yet to respond to my post I am responding to it after several days of research. I was able to integrated with S#harp with 1 interface and 3 classes (I was hoping their would be an out of the box solution?).
The code provided below can be copied and pasted to any application and it should just work. The only exception is the FederationSessionHelper class. This is specific to each application as to were you are getting the info may change. I have an app setting section within my web.config that has the Federation name etc. Also, when the user authenticates, I parse the root url they are comming from then query the Federation Root to find out what tenant they are (I have a custom Tenant table I created). I then place the tenant ID in session under key "FederationKeyValue_Key" which will then be used in the FederationSession class to build the Use Federation statement.
/// <summary>
/// Interface used to retrieve app specific info about your federation.
/// </summary>
public interface IFederationSessionHelper
{
string ConnectionString { get; }
string FederationName { get; }
string DistributionName { get; }
string FederationKeyValue { get; }
}
/// <summary>
/// This is were you would get things specific for your application. I have 3 items in the web.config file and 1 stored in session. You could easily change this to get them all from the repository or wherever meets the needs of your application.
/// </summary>
public class FederationSessionHelper : IFederationSessionHelper
{
private const string ConnectionStringKey = "ConnectionString_Key";
private const string FederationNameKey = "FederationName_Key";
private const string DistributionNameKey = "DistributionName_Key";
private const string FederationKeyValueKey = "FederationKeyValue_Key";
public string ConnectionString { get { return ConfigurationManager.ConnectionStrings[ConnectionStringKey].ConnectionString; } }
public string FederationName { get { return ConfigurationManager.AppSettings[FederationNameKey]; } }
public string DistributionName { get { return ConfigurationManager.AppSettings[DistributionNameKey]; } }
//When user authenitcates, retrieve key value and store in session. This will allow to retrieve here.
public string FederationKeyValue { get { return Session[FederationKeyValueKey]; } }
}
/// <summary>
/// This is were the magic begins and where the integration with S#arp occurs. It manually creates a Sql Connections and adds it the S#arps storage. It then runs the Use Federation command and leaves the connection open. So now when you use an NhibernateSession.Current it will work with Sql Azure Federation.
/// </summary>
public class FederationSession : IDisposable
{
private SqlConnection _sqlConnection;
public void Init(string factoryKey,
string federationName,
string distributionName,
string federationKeyValue,
bool doesFilter,
string connectionString)
{
var sql = string.Format("USE FEDERATION {0}({1} = '{2}') WITH RESET, FILTERING = {3};", federationName, distributionName, federationKeyValue, (doesFilter) ? "ON" : "OFF");
_sqlConnection = new SqlConnection(connectionString);
_sqlConnection.Open();
var session = NHibernateSession.GetSessionFactoryFor(factoryKey).OpenSession(_sqlConnection);
NHibernateSession.Storage.SetSessionForKey(factoryKey, session);
var query = NHibernateSession.Current.CreateSQLQuery(sql);
query.UniqueResult();
}
public void Dispose()
{
if (_sqlConnection != null && _sqlConnection.State != ConnectionState.Closed)
_sqlConnection.Close();
}
}
/// <summary>
/// This was just icing on the cake. It inherits from S#arps TransactionAttribute and calls the FederationSession helper to open a connection. That way all you need to do in decorate your controller with the newly created [FederationTransaction] attribute and thats it.
/// </summary>
public class FederationTransactionAttribute : TransactionAttribute
{
private readonly string _factoryKey = string.Empty;
private bool _doesFilter = true;
/// <summary>
/// When used, assumes the <see cref = "factoryKey" /> to be NHibernateSession.DefaultFactoryKey
/// </summary>
public FederationTransactionAttribute()
{ }
/// <summary>
/// Overrides the default <see cref = "factoryKey" /> with a specific factory key
/// </summary>
public FederationTransactionAttribute(string factoryKey = "", bool doesFilter = true)
: base(factoryKey)
{
_factoryKey = factoryKey;
_doesFilter = doesFilter;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var federationSessionHelper = ServiceLocator.Current.GetInstance<IFederationSessionHelper>();
var factoryKey = GetEffectiveFactoryKey();
new FederationSession().Init(factoryKey,
federationSessionHelper.FederationName,
federationSessionHelper.DistributionName,
federationSessionHelper.FederationKeyValue,
_doesFilter,
federationSessionHelper.ConnectionString);
NHibernateSession.CurrentFor(factoryKey).BeginTransaction();
}
private string GetEffectiveFactoryKey()
{
return String.IsNullOrEmpty(_factoryKey) ? SessionFactoryKeyHelper.GetKey() : _factoryKey;
}
}
Now I am able to replace S#arp's [Transaction] attribute with the newly created [FederationTransaction] as follows:
[HttpGet]
[FederationTransaction]
public ActionResult Index()
{
var viewModel = NHibernateSession.Current.QueryOver<SomeDemoModel>().List()
return View(viewModel);
}
None of the code within the Controller needs to know that its using Sql Azure Federation. It should all just work.
Any thoughts? Anyone found a better solution? Please, share.

OData / WCF Data Service not working with complex type

I'm brand new to OData and WCF data services so this might be an easy problem. I'm using VS Web Developer Express 2010 where I have a very simple WCF Data Service hosted in a console app. It's returning an IQuerable collection of a simple 'Study' class from a repository (located in a separated dll project), which in turn retrieves 'Study' classes from a db project in another dll (so 3 projects in the solution).
I also have an 'Experiment' class in the db project and there can be multiple Experiments in a Study. When I exclude the Experiment class from the Study everything works and I get data coming back. The problem happens when I add a List collection to the Study class, then I get a runtime error when I try to run the service. In Firebug the error is '500 Internal Server Error', and the message in the browser is 'Request Error. The server encountered an error processing the request. See server logs for more details.'
I have IIS 7 and I also just installed IIS 7.5 but again it's brand new to me, so I can't figure out where the service is hosted or where to view the server / web logs. There are only IIS 7 logs visible in 'C:\inetpub\logs\LogFiles\W3SVC1'. The VS web server (Cassini) doesn't start when I run the app, so this suggests it's being hosted in IIS 7.5 (?).
So
- how do I return child classes / complex objects?
- how do I know where my service is hosted and where can I find the server logs?
Here's the host app:
using MyStudyRepository;
using MyStudyDB;
namespace MyStudyService
{
public class Program
{
public static void Main(string[] args)
{
string serviceAddress = "http://localhost:998";
Uri[] uriArray = { new Uri(serviceAddress) };
Type serviceType = typeof(StudyDataService);
using (var host = new DataServiceHost(serviceType,uriArray))
{
host.Open();
Console.WriteLine("Press any key to stop service");
Console.ReadKey();
}
}
}
public class StudyDataService : DataService<StudyRepository>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
}
}
}
Here's the repository:
using MyStudyDB;
namespace MyStudyRepository
{
public class StudyRepository
{
List<Study> _List = new List<Study>();
//Add constructor to populate myStudies list on creation of class
public StudyRepository()
{
for (int i = 1; i < 5; i++)
{
Study myStudy = new Study() { ID = i, StudyOwnerId = i, StudyName = "Study" + i.ToString() /*, Experiments = null */ };
_List.Add(myStudy);
}
}
public IQueryable<Study> Studies
{
get
{
return _List.AsQueryable<Study>();
}
}
}
}
And here's the DB:
namespace MyStudyDB
{
public class Study
{
public int ID { get; set;}
public int StudyOwnerId { get; set; }
public string StudyName { get; set; }
//public List<Experiment> Experiments { get; set; }
}
public class Experiment
{
public int ID { get; set; }
public string Name { get; set; }
public int StudyId { get; set; }
}
}
To debug the WCF Data Service please refer to this blog post: http://blogs.msdn.com/b/phaniraj/archive/2008/06/18/debugging-ado-net-data-services.aspx
As to why the collection of Experiment doesn't work, there are two reasons:
The Experiment class is not recognized as an entity type because there's no entity set for it. (Entity set is the IQueryable property on your repository class, which you don't have). As a result the Experiment class is only recognized as a complex type.
The currently released version of WCF Data Services doesn't support MultiValues, MultiValue is effectively a collection of primitive or complex types.
So you have two way to "fix" this. Either make sure that Experiment is in fact an entity, by adding IQueryable property on your repository class.
Or use the latest CTP (http://blogs.msdn.com/b/astoriateam/archive/2011/06/30/announcing-wcf-data-services-june-2011-ctp-for-net4-amp-sl4.aspx) which does support MultiValues.
Thanks! And I guess it is missing the DataServiceKey attribute on the class as follows:
[DataServiceKey("ID")]
public class Study
{
.....
}