How to add Bing Spellcheck to LuisRecognizerMiddleware? - c#

Ok so this is how my LUIS app is configured in my bot.
On the LUIS website I can add Bing Spell Check to correct common spelling mistakes and have a better intent and entity match.
All that is required is that a BING API key needs to be added to the LUIS query string. But where do I configure that in the LuisRecognizerMiddleware?
I'm not even sure if that's the right place. But I guess it does put together the URI.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(_configuration);
options.Middleware.Add(new CatchExceptionMiddleware<Exception>(async (context, exception) =>
{
await context.TraceActivity("MyBotException", exception);
await context.SendActivity("Sorry, it looks like something went wrong!");
}));
IStorage dataStore = new MemoryStorage();
options.Middleware.Add(new ConversationState<MyBotConversationState>(dataStore));
// Add LUIS recognizer as middleware
// see https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs
(string modelId, string subscriptionKey, Uri url) = GetLuisConfiguration(_configuration);
LuisModel luisModel = new LuisModel(modelId, subscriptionKey, url);
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel));
});
}
private static (string modelId, string subscriptionKey, Uri url) GetLuisConfiguration(IConfiguration configuration)
{
string modelId = configuration.GetSection("Luis-ModelId")?.Value;
string subscriptionKey = configuration.GetSection("Luis-SubscriptionId")?.Value;
string url = configuration.GetSection("Luis-Url")?.Value;
Uri baseUri = new Uri(url);
return (modelId, subscriptionKey, baseUri);
}
All I get so far is...
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&q=test234&log=True HTTP/1.1
What I expect is something among those lines (copied from the LUIS web portal)
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&spellCheck=true&bing-spell-check-subscription-key=&verbose=true&timezoneOffset=0&q=test234

I just had a quick glimpse at the source code and figured ILuisOptions is what I am looking for. There was no concrete implementation to that. It's "roll your own" I guess...
public class MyLuisOptions : ILuisOptions
{
public bool? Log { get; set; }
public bool? SpellCheck { get; set; }
public bool? Staging { get; set; }
public double? TimezoneOffset { get; set; }
public bool? Verbose { get; set; }
public string BingSpellCheckSubscriptionKey { get; set; }
}
...and of course you have to pass this along to the LuisRecognizerMiddleware.
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel, new LuisRecognizerOptions { Verbose = true }, new MyLuisOptions { SpellCheck = true, BingSpellCheckSubscriptionKey = "test123" }));

Related

ModelState.IsValid show true for invalid entry on Test

I have class city:
public class City
{
public int Id { get; set; }
[Required(ErrorMessage = "This field is required (server validation)")]
public string Name { get; set; }
[Range(1, 100000000, ErrorMessage = "ZIP must be greater than 1 and less than 100000000 (server validation)")]
public int ZIP { get; set; }
[Range(1, 2000000000, ErrorMessage = "Population must be between 1 and 2B (server validation)")]
public int Citizens { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
I have in controller post action for add city:
[HttpPost]
public IActionResult PostCity(City city)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_cityRepository.Add(city);
return CreatedAtAction("GetCity", new { id = city.Id }, city);
}
and I have test for invalid model:
[Fact]
public void PostCity_InvalidModel_ReturnsBadRequest()
{
// Arrange
City city = new City() { Id=15, Name = "", ZIP = 0, CountryId = 1, Citizens = 0 };
var mockRepository = new Mock<ICityRepositroy>();
var mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile(new CityProfile()));
IMapper mapper = new Mapper(mapperConfiguration);
var controller = new CitysController(mockRepository.Object, mapper);
// Act
var actionResult = controller.PostCity(city) as BadRequestResult;
// Assert
Assert.NotNull(actionResult);
}
I debug test and I always get modelState.IsValid = true. When I try in postman to send invalid request, server validation works fine. Why my validation doesn't work in my test? ASP .Net Core framework is 5.0.
Your test call the method controller.PostCity(city) directly, while in the web server, when calling the endpoint, a whole bunch of middle ware is triggered and executed first.
One of this being the modelbinder, which I believe also performs the model validation.
So this will not work, because calling the ModelState.IsValid without any further initialization, causes it to return true as by default:
var controller = new CitysController(mockRepository.Object, mapper);
// Act
var actionResult = controller.PostCity(city) as BadRequestResult;
In stead you either need to connect the proper validation mechanism of the model, or even better (IMO) use the test web server which can do this in an easy controllable way.
Here's basically how to do it, but I recommend you to read some of the following articles as well (source: MSDN):
public class PrimeWebDefaultRequestShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebDefaultRequestShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Hello World!", responseString);
}
}
In the code above you'll need to replace the likes like EnsureSuccessStatusCode with you actual BadRequest test case.
Some additional reads:
https://www.meziantou.net/testing-an-asp-net-core-application-using-testserver.htm
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/multi-container-microservice-net-applications/test-aspnet-core-services-web-apps
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
https://www.roundthecode.com/dotnet/asp-net-core-web-api/asp-net-core-testserver-xunit-test-web-api-endpoints
More info on the pipelines, by MSDN:
It's because when you start the WebApi, and send a real request to it, behind the scenes it calls several methods in which it's validating your request, before it calls your controller action. In those methods it's setting the ModelState.IsValid property to false. On the other hand when you call your controller action directly from tests, nothing validates your request, hence your ModelState.IsValid is set to true by default.

Proper way to use Azure Keyvault with Dependency Injection

Information
I am building an Azure hosted protected API. I am targeting .Net 5 and using the MVC pattern. Inside this API, I have two DAL (data access layer) classes, which are added to the service container via the Startup.cs file. Both of these classes are Transient and expect a connection string in their constructors.
Problem
I want to move the connection strings from the appsetting.json file of the API to the Azure key vault, but I only want to have to pull down the connection strings once from the Azure key vault and use them as static values to initialize any instances of the DAL classes. I've tried calling the respective method of the SecretClient class called GetSecretAsync and I use the constructor of SecretClient(Uri, ManagedIdentityCredential). This all works when I call this after Startup.cs has ran, but when I try to do it inside of the Startup.cs it seems to hang for some unknown reason. I know that the calls do work later in the pipeline as I've tested them in the API as well as in the Azure Portal. This is what I've been trying to do via code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//For Production, set the properties of the KeyVault
Secrets.SetProps(new KeyVaultHelper(Configuration)).GetAwaiter().GetResult();
//Get the AzureAd section
try
{
//Instead of storing the AzureAdBinding info in appsettings, I created a
//json string which deserializes into a Dictionary which is used to
//create the ConfigurationSection object
Dictionary<string, string> azureadkvps = JsonConvert.DeserializeObject<Dictionary<string, string>>(Secrets.AzureAdBinding);
//Turn it into an IConfigurationSection
IConfigurationSection azureconfigsection = new ConfigurationBuilder().AddInMemoryCollection(azureadkvps).Build().GetSection("AzureAd");
//Bind authentication to Configuration AzureAd section
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(azureconfigsection);
}
catch { }
Secrets.cs
/// <summary>
/// Static class to hold values of keys pulled from Azure key vault
/// </summary>
public static class Secrets
{
private const string MOBILE_APP_ID = "xxxxxxxxxx";
private const string B_DB_CONN = "xxxxxxxxxx";
private const string MOBILE_ADMIN_ROLE = "xxxxxxxxxx";
private const string MOBILE_API_SCOPES = "xxxxxxxxxx";
private const string MOBILE_COMMON_ROLE = "xxxxxxxxxx";
private const string BL_DB_CONN = "xxxxxxxxxx";
private const string AZUREAD_BINDING = "xxxxxxxxxx";
public static string BLConn { get; private set; } = null;
public static string BConn { get; private set; } = null;
public static string MobileAdminRole { get; private set; } = null;
public static string MobileUserRole { get; private set; } = null;
public static string MobileApiScopes { get; private set; } = null;
public static string MobileClientID { get; private set; } = null;
public static string AzureAdBinding { get; private set; } = null;
public async static Task SetProps(IKeyVaultHelp keyvault)
{
Task<string>[] secretretrievaltasks = new Task<string>[7];
secretretrievaltasks[0] = keyvault.GetSecretAsync(MOBILE_APP_ID);
secretretrievaltasks[1] = keyvault.GetSecretAsync(B_DB_CONN);
secretretrievaltasks[2] = keyvault.GetSecretAsync(MOBILE_ADMIN_ROLE);
secretretrievaltasks[3] = keyvault.GetSecretAsync(MOBILE_API_SCOPES);
secretretrievaltasks[4] = keyvault.GetSecretAsync(MOBILE_COMMON_ROLE);
secretretrievaltasks[5] = keyvault.GetSecretAsync(BL_DB_CONN);
secretretrievaltasks[6] = keyvault.GetSecretAsync(AZUREAD_BINDING);
await Task.WhenAll(secretretrievaltasks).ConfigureAwait(false);
BLConn = secretretrievaltasks[5].Result;
BConn = secretretrievaltasks[1].Result;
MobileAdminRole = secretretrievaltasks[2].Result;
MobileUserRole = secretretrievaltasks[4].Result;
MobileApiScopes = secretretrievaltasks[3].Result;
MobileClientID = secretretrievaltasks[0].Result;
AzureAdBinding = secretretrievaltasks[6].Result;
}
}
KeyVaultHelper.cs
public class KeyVaultHelper : IKeyVaultHelp
{
private readonly IConfiguration _config;
public KeyVaultHelper(IConfiguration config)
{
_config = config;
}
public string GetSecret(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential("xxxxxxxxxxxxxxxxxxxx"));
return secretclient.GetSecret(secretkey).Value.Value;
}
public async Task<string> GetSecretAsync(string secretkey)
{
string kvuri = $"https://{_config.GetValue<string>("KVN")}.vault.azure.net";
var secretclient = new SecretClient(new Uri(kvuri), new ManagedIdentityCredential());
return (await secretclient.GetSecretAsync(secretkey).ConfigureAwait(false)).Value.Value;
}
}
Perhaps I am going about this all wrong, but I'd like to be able to pull these values before adding the DAL classes to the service container so that the static values of the Secrets.cs hold the correct connection strings which will be used to construct the DAL objects.
I understand there is a way to add a Configuration file in Azure which can be built in Program.cs via the following, but I was hoping to just pull the values directly from KeyVault and use them throughout the lifetime of the application. Any direction or advice is appreciated.
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
var settings = config.Build();
config.AddAzureAppConfiguration(options =>
{
options.Connect(settings["ConnectionStrings:AppConfig"])
.ConfigureKeyVault(kv =>
{
kv.SetCredential(new DefaultAzureCredential());
});
});
})
.UseStartup<Startup>());
}
Thanks Camilo Terevinto, your comments are converting to an answer. So that it may helps to other community members to find this correct solution.
Instead in the Startup, use await with async Task Main in Program class where you already have the IConfiguration configured.
use AddAzureAppConfiguration if you're using app services, or
put the secrets in environment variables if you're running on VMs/containers
To populate the static values before the call to CreateHostBuilder(args).Build().Run(), you should call the async or await method.

Custom IConfiguration Service in Client SIde Blazor

I am trying to have some basic configuration from json file to a singleton service inside my client side blazor application at the start up.
Below is my code setup
AppConfig and IAppConfig files
interface IAppConfig
{
string BaseUrl { get; set; }
string Source { get; set; }
}
and
public class AppConfig : IAppConfig
{
public string BaseUrl { get; set; }
public string Source { get; set; }
}
Than a json file by the name of environment.json inside wwwroot as wwwroot/ConfigFiles/environment.json
Than a service to read this file
interface ISharedServices
{
Task<AppConfig> GetConfigurationAsync();
}
and
public class SharedServices : ISharedServices
{
private HttpClient Http { get; set; }
public SharedServices(HttpClient httpClient)
{
Http = httpClient;
}
public async Task<AppConfig> GetConfigurationAsync()
{
return await Http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json");
}
}
Now i am calling it into my component which load first
public class IndexComponent : ComponentBase
{
[Inject]
internal IAppConfig AppConfig { get; set; }
[Inject]
internal ISharedServices sharedServices { get; set; }
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig = appconfig;
}
}
All this works fine , but i want to have this configuration ready at the time of application load in browser , so as suggested by "auga from mars" in my other Question i tried below code inside startup.cs at the moment i add IAppConfig as singleton service
services.AddSingleton<IAppConfig, AppConfig>(provider =>
{
var http = provider.GetRequiredService<HttpClient>();
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
});
But , buy using this code the blazor app never start up , all it show a blank white page with text Loading.... , not even any error but in every 5 min pop up show - page taking too much time to load with two option of wait and close .
If i change this code a bit from
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").GetAwaiter().GetResult();
to
return http.GetJsonAsync<AppConfig>("ConfigFiles/environment.json").Result;
Than it say - "Maximum call stack size exceed"
How to have configuration ready at startup ??
Update 1:
A little Update
in Basecomponent file , code is
protected override async Task OnInitializedAsync()
{
var appconfig = await sharedServices.GetConfigurationAsync();
AppConfig.BaseUrl = appconfig.BaseUrl;
AppConfig.Source = appconfig.Source;
}
I have to set every property one by one manually , need to get rid of this too

How to authenticate facebook web api in asp.net core 2

I'm currently building my very first mobile app in Xamarin.Forms. The app has a facebook login and after the user has been logged in I'm storing the facebook token because I want to use it as a bearer-token to authenticate any further requests against an API.
The API is a .NET core 2.0 project and I am struggling to get the authentication working.
In my Xamarin.Forms app the facebook token is set as bearer-token with the following code;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", UserToken);
As far as I know, this properly sets the bearer token in the headers of the request.
I've talked to a colleague of mine about this and he told me to take a look at Identityserver4 which should support this. But for now, I've decided not to do that since for me, at this moment, it is overhead to implement this. So I've decided to stay with the idea to use the facebook token as bearer token and validate this.
So the next step for me is to find a way to authenticate the incoming bearer token with Facebook to check if it is (still) valid.
So I've configured the startup for my API projects as following;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddFacebook(o =>
{
o.AppId = "MyAppId";
o.AppSecret = "MyAppSecret";
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Enable authentication
app.UseAuthentication();
//Enable support for default files (eg. default.htm, default.html, index.htm, index.html)
app.UseDefaultFiles();
//Configure support for static files
app.UseStaticFiles();
app.UseMvc();
}
}
But when I'm using postman to do a request and test if everything is working I'm receiving the following error;
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
What am I doing wrong here?
EDIT:
In the mean time if been busy trying to find a solution for this. After reading a lot on Google it seems thatadding an AuthorizationHandler is the way to go at the moment. From there on I can make request to facebook to check if the token is valid. I've added the following code to my ConfigureServices method;
public void ConfigureServices(IServiceCollection services)
{
//Other code
services.AddAuthorization(options =>
{
options.AddPolicy("FacebookAuthentication", policy => policy.Requirements.Add(new FacebookRequirement()));
});
services.AddMvc();
}
And I've created a FacebookRequirement which will help me handling the policy;
public class FacebookRequirement : AuthorizationHandler<FacebookRequirement>, IAuthorizationRequirement
{
private readonly IHttpContextAccessor contextAccessor;
public FacebookRequirement(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FacebookRequirement requirement)
{
//var socialConfig = new SocialConfig{Facebook = new SocialApp{AppId = "MyAppId", AppSecret = "MyAppSecret" } };
//var socialservice = new SocialAuthService(socialConfig);
//var result = await socialservice.VerifyFacebookTokenAsync()
var httpContext = contextAccessor.HttpContext;
if (httpContext != null && httpContext.Request.Headers.ContainsKey("Authorization"))
{
var token = httpContext.Request.Headers.Where(x => x.Key == "Authorization").ToList();
}
context.Succeed(requirement);
return Task.FromResult(0);
}
}
The problem I'm running into now is that I don't know where to get the IHttpContextAccessor. Is this being injected somehow? Am I even on the right path to solve this issue?
I ended up creating my own AuthorizationHandler to validate incoming requests against facebook using bearer tokens. In the future I'll probably start using Identityserver to handle multiple login types. But for now facebook is sufficient.
Below is the solution for future references.
First create an FacebookRequirement class inheriting from AuthorizationHandler;
public class FacebookRequirement : AuthorizationHandler<FacebookRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FacebookRequirement requirement)
{
var socialConfig = new SocialConfig { Facebook = new SocialApp { AppId = "<FacebookAppId>", AppSecret = "<FacebookAppSecret>" } };
var socialservice = new SocialAuthService(socialConfig);
var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
if (authorizationFilterContext == null)
{
context.Fail();
return Task.FromResult(0);
}
var httpContext = authorizationFilterContext.HttpContext;
if (httpContext != null && httpContext.Request.Headers.ContainsKey("Authorization"))
{
var authorizationHeaders = httpContext.Request.Headers.Where(x => x.Key == "Authorization").ToList();
var token = authorizationHeaders.FirstOrDefault(header => header.Key == "Authorization").Value.ToString().Split(' ')[1];
var user = socialservice.VerifyTokenAsync(new ExternalToken { Provider = "Facebook", Token = token }).Result;
if (!user.IsVerified)
{
context.Fail();
return Task.FromResult(0);
}
context.Succeed(requirement);
return Task.FromResult(0);
}
context.Fail();
return Task.FromResult(0);
}
}
Add the following classes which will contain the configuration an represent the user;
public class SocialConfig
{
public SocialApp Facebook { get; set; }
}
public class SocialApp
{
public string AppId { get; set; }
public string AppSecret { get; set; }
}
public class User
{
public Guid Id { get; set; }
public string SocialUserId { get; set; }
public string Email { get; set; }
public bool IsVerified { get; set; }
public string Name { get; set; }
public User()
{
IsVerified = false;
}
}
public class ExternalToken
{
public string Provider { get; set; }
public string Token { get; set; }
}
And last but not least, the SocialAuthService class which will handle the requests with facebook;
public class SocialAuthService
{
private SocialConfig SocialConfig { get; set; }
public SocialAuthService(SocialConfig socialConfig)
{
SocialConfig = socialConfig;
}
public async Task<User> VerifyTokenAsync(ExternalToken exteralToken)
{
switch (exteralToken.Provider)
{
case "Facebook":
return await VerifyFacebookTokenAsync(exteralToken.Token);
default:
return null;
}
}
private async Task<User> VerifyFacebookTokenAsync(string token)
{
var user = new User();
var client = new HttpClient();
var verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}&fields=email,name", token);
var verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", token);
var uri = new Uri(verifyTokenEndPoint);
var response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
dynamic userObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
if (appObj["id"] == SocialConfig.Facebook.AppId)
{
//token is from our App
user.SocialUserId = userObj["id"];
user.Email = userObj["email"];
user.Name = userObj["name"];
user.IsVerified = true;
}
return user;
}
return user;
}
}
This will validate the Facebook token coming from the request as bearer token, with Facebook to check if it's still valid.

Return Custom Object from AuthenticationHandler

I'm attempting to utilize custom authentication from a 3rd party provider - and link this in to .net core 2.0.
I've created the basics...
"TokenAuthenticationHandler"
public class TokenAuthenticationHandler : AuthenticationHandler<TokenAuthenticationOptions>
{
public TokenAuthenticationHandler(IOptionsMonitor<TokenAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Get the API key
var token = new AuthToken
{
ApiKey = GetKeyValue(Options.ApiKeyName),
Username = GetKeyValue(Options.UsernameKeyName),
Password = GetKeyValue(Options.PasswordKeyName),
IpAddress = Context.Connection.RemoteIpAddress.ToString()
};
// setup the auth repo and identity
var authRepo = new AuthRepository(token);
var identity = new TokenIdentity(authRepo);
// Check the identity
if (identity.IsAuthenticated)
{
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), null, "exttoken");
var result = AuthenticateResult.Success(ticket);
return result;
}
// Authentication failed
return AuthenticateResult.NoResult();
}
protected string GetKeyValue(string keyName)
{
return Request
.Headers?
.SingleOrDefault(a => a.Key == keyName)
.Value
.FirstOrDefault();
}
}
"TokenAuthenticationOptions"
public class TokenAuthenticationOptions : AuthenticationSchemeOptions
{
public string ApiKeyName { get; set; } = "X-APIKEY";
public string UsernameKeyName { get; set; } = "X-USERNAME";
public string PasswordKeyName { get; set; } = "X-PASSWORD";
public string CookieName { get; set; } = "MTDATA";
}
This all works perfectly, the user is authenticated, or not (via a 401 error), and the controller is called...
However... I somehow need to get the "AuthRepository" object from here - back to my controller, as this is how I interact with the 3rd party system.
I attempted to resolve this with a custom IIdentity implementation, as seen below;
public class TokenIdentity : IIdentity
{
public string AuthenticationType { get; } = "exttoken";
public bool IsAuthenticated { get; }
public string Name { get; }
public AuthRepository AuthenticationRepository { get; }
public TokenIdentity(AuthRepository authRepository)
{
AuthenticationRepository = authRepository;
IsAuthenticated = AuthenticationRepository.Authenticate();
if (IsAuthenticated)
Name = AuthenticationRepository.GetCurrentUser()?.Name;
}
}
Within my controller, I then attempt to get the Identity with HttpContext.User.Identity - however at this point within the controller, my customer "TokenIdentity" has been transformed into a "ClaimsPrinciple", the error listed is:
System.InvalidCastException: 'Unable to cast object of type
'System.Security.Claims.ClaimsIdentity' to type
'X.X.X.WebAPI.Authentication.TokenIdentity'.'
Any ideas? Attempting to call the authRepository again is not an option, as there is overheads associated with the authorization and access request - its therefore vital I continue to utilize the existing authRepo object.
Resolved this issue with the following change to the HandleAuthenticateAsync;
// Check the identity
if (identity.IsAuthenticated)
{
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), null, "exttoken");
var result = AuthenticateResult.Success(ticket);
Request.HttpContext.Items["auth"] = authRepo;
return result;
}
I can then access the authRepo with:
var authRepo = (AuthRepository)HttpContext.Items["auth"];
Is it a good idea to store this object in HttpContext.Items?

Categories

Resources