Authentication.Challenge not working with ApiController - single-sign-on

With ApiController, Authentication.Challenge not prompting Microsoft login for SSO. it executes SignIn action method, with out any errors. If I change from ApiController to Controller then it's prompting. does any one know how to prompt for Microsoft login using ApiController?
public class ValuesController : ApiController
{
[System.Web.Http.Route("api/values/signin")]
[System.Web.Http.HttpGet]
public void SignIn()
{
if (!System.Web.HttpContext.Current.Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
public class ValuesController : Controller
{
public void SignIn()
{
if (!System.Web.HttpContext.Current.Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}

We also faced a similar problem on our product.
The issue was the following: Challenge sets 401 status code for current response, which is later handled by a responsible OWIN Middleware, so if status code is not 401 the middleware won't handle the response and won't trigger the redirect.
But the default behavior of void action of ApiController sets 204 response status code. Therefore 401 is overwritten with 204, as a result nothing happens.
So there are several solutions:
Don't use ApiController if you can
Use ApiController but not void action. Use for example something like this
public ActionResult SignIn()
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(...);
return new HttpStatusCodeResult(HttpContext.GetOwinContext().Response.StatusCode);
}
If you have to use a void method and ApiController then you can end the response and then the status code won't be modified.
public void SignIn()
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(...);
System.Web.HttpContext.Current.Response.End();
}

Related

How do I get the response status code of a void method?

/I'm trying to use a Feign-client to communicate another rest service
which will return status code 204 with no body/
public interface DepartmentApi {
#RequestLine("GET /department/nocontent") /*Department Client*/
#Headers("Content-Type: application/json")
ResponseEntity<Void> getDepartment();
}
#Component
public class ClientApiFactory {
#Bean
#RequestScope
public DepartmentApi getDepartmentApi() { /*Bean for Department client */
return HystrixFeign.builder()
.logLevel(Logger.Level.BASIC)
.decoder(new JacksonDecoder())
.encoder(new JacksonEncoder())
.target(DepartmentApi.class, "http://localhost:8080");
}
}
#GetMapping(value = "/nocontent") /*Department Service which is running on 8080*/
ResponseEntity<Void> noContent() {
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
I would like to retrieve the status code from the response for the void methods, but with a void method there is no way to get to the status ,it's returns[ReponseEntity] null.
Is there a way to retrieve the HTTP status code from a Feign method for a resource that returns no body? They all fail with a nullpointer exception because of the lack of response body.

Capture Events From Microsoft.Identity.Web Login/Logout

I am using Microsoft's Authentication/Authorization platform to allow User Sign-ins from Azure AD. I would like to log these events into a database. Issue is, since this type of authentication leverages middleware I am not sure how to inject code to trigger a log event.
Please let me know if there exists documentation I haven't yet found and/or how to write up a custom injection to log these events.
Thanks!
I solved my own problem. For any potential usefulness to anyone else in the future I will add what I did below..
I set up my database according to this documentation: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/adding-model?view=aspnetcore-5.0&tabs=visual-studio
I created this Middleware Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Identity.Web;
using Application.Models;
using Application.Data;
namespace Application.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class EventLogCaptureMiddleware
{
private readonly RequestDelegate _next;
private readonly EventLogContext _context;
public EventLogCaptureMiddleware(RequestDelegate next, EventLogContext context)
{
_next = next;
_context = context;
}
public Task Invoke(HttpContext httpContext)
{
var eventLogModel = new EventLogViewModel
{
Timestamp = DateTime.Now,
Type = "TEST",
Method = httpContext.Request.Method,
Upn = httpContext.User.Identity.Name,
Resource = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}{httpContext.Request.Path}"
};
_context.Add(eventLogModel);
var tasks = new Task[] { _context.SaveChangesAsync() };
Task.WaitAll(tasks);
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class EventLogCaptureMiddlewareExtensions
{
public static IApplicationBuilder UseEventLogCaptureMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<EventLogCaptureMiddleware>();
}
}
}
And injected into Startup.cs likeso:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//Production Exception Handler ex: API connection failed will trigger exception routed to /Home/Error
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//Handles User Error: 401, 403, 404, etc. Errors caught must land Application side. Errors occured in API with return 500 and be routed via Exception Handler
app.UseStatusCodePagesWithReExecute("/Home/Error", "?status={0}");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
//Must include Authentication/Authorization under routing
app.UseAuthentication();
app.UseAuthorization();
app.UseEventLogCaptureMiddleware();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}

Call an api as part of startup of an application?

I am currently in a situation where I need to call a controller as part of the startup of an application?
The controller is being hosted by the application itself..
Is that somehow possible? It just needs to be triggered every time the application starts.
I ended implementing an interface and implement the required functionality within this interface.
IControllerService.cs
public interface IControllerService
{
void InsertIntoDB(string name);
}
Controller.cs
public InsertIntoDB(string name)
{
....
}
so I in my Startup.Configure could call
startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, SchemaContext schemaContext, IControllerService controllerService)
{
....
controllerService.InsertIntoDB("InitData")
}
My API endpoint uses the same interface to call out
You could inject IHostApplicationLifetime on Startup.Configure() method , then write the callback for ApplicationStarted that would be triggered when the application host has fully started, and call your controller action within callback method.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
//register other services
}
private async Task<Action> OnApplicationStartedAsync(IHttpClientFactory httpClientFactory)
{
var client = httpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44326/api/values");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
//deal with the response
var result = await response.Content.ReadAsStringAsync();
}
return null;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{
IHttpClientFactory _clientFactory = app.ApplicationServices.GetService(typeof(IHttpClientFactory)) as IHttpClientFactory;
lifetime.ApplicationStarted.Register(OnApplicationStartedAsync(_clientFactory).Wait);
//other middlewares
}
In your Startup, you could call:
public void ConfigureServices(IServiceCollection services)
{
...
...
services.AddTransient<Interfaces.IService, Service.ServiceImplementator>();
...
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
...
Task.Run(() => {
app.ApplicationServices.GetRequiredService<Interfaces.IService>().DoWorkOnStartup();
});
...
...
}
Don't call a controller action, I think your controller should be invoking a service to do the work.

ASP.NET Core 2: Entity Framework Context is disposed too early in asynchronous PayPal IPN. How do I get it back in a later thread?

I have an endpoint which is receiving IPN activity from PayPal. Here is the POST Action that is taken straight from their docs with manual modifications:
[HttpPost]
public IActionResult Receive()
{
IPNContext ipnContext = new IPNContext()
{
IPNRequest = Request
};
using (StreamReader reader = new StreamReader(ipnContext.IPNRequest.Body, Encoding.ASCII))
{
ipnContext.RequestBody = reader.ReadToEnd();
}
List<KeyValuePair<string, string>> ipnVarsWithCmd = ipnContext.RequestBody.Split('&')
.Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
//Fire and forget verification task -- ** THIS **
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
//Reply back a 200 code
return Ok();
}
The issue is the indicated line. This is a "fire and forget" route, and is executed asynchronously. When the Action is complete, and returns Ok, I am assuming that the injected Entity Framework context from the controller:
public class IPNController : Controller
{
private readonly EFContext _context;
public IPNController(EFContext context)
{
_context = context;
}
}
... gets Disposed? According to my logs, it looks like it.
Meanwhile, I have that second thread doing the actual legwork of the IPN request which needs that EFContext to be around.
Is there a pattern I am missing here? (Bearing in mind whilst I'm not new to .NET I am to .NET Core)
Or is there a way I can "get it back" so I can use it?
Update:
You might find my initialisation of the context useful:
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.AddDbContext<EFContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
}
Change:
Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
to
await Task.Run(() => VerifyTask(ipnContext, ipnVarsWithCmd));
and method declaration to:
public async Task<IActionResult> Receive()
Also wrap IPNContext to using block to let it dispose when it is not needed.

Xamarin.Auth: Using Facebook oauth, how to redirect to my app?

I've just started using Xamarin.Auth and I want to enable Facebook login via oauth.
Here is my config:
public static string ClientId = "client id";
public static string ClientSecret = "client secret";
public static string Scope = "email";
public static string AuthorizeUrl = "https://m.facebook.com/dialog/oauth";
public static string RedirectUrl = "https://www.facebook.com/connect/login_success.html";
public static string AccessTokenUrl = "https://m.facebook.com/dialog/oauth/token";
Code for initiating the authentication:
public class AuthenticationPageRenderer : PageRenderer
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear (animated);
var auth = new OAuth2Authenticator (
Constants.ClientId,
Constants.ClientSecret,
Constants.Scope,
new Uri (Constants.AuthorizeUrl),
new Uri (Constants.RedirectUrl),
new Uri (Constants.AccessTokenUrl)
);
auth.Completed += OnAuthenticationCompleted;
PresentViewController (auth.GetUI (), true, null);
}
async void OnAuthenticationCompleted (object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine ("AUTH Completed!");
if (e.IsAuthenticated) {
}
}
}
Seems to work fine, but instead of redirecting the user to https://www.facebook.com/connect/login_success.html, I want to redirect him back to my app again. Any help much appreciated!
Best,
Sascha
You can "redirect back" to your app again by simply invoking your own method to display the app's page you want to show to your user like this.
async void OnAuthenticationCompleted (object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine ("AUTH Completed!");
if (e.IsAuthenticated) {
//invoke the method that display the app's page
//that you want to present to user
App.SuccessfulLoginAction.Invoke();
}
}
In your App.cs
public static Action SuccessfulLoginAction
{
get
{
return new Action(() =>
{
//show your app page
var masterDetailPage = Application.Current.MainPage as MasterDetailPage;
masterDetailPage.Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(MainPage)));
masterDetailPage.IsPresented = false;
});
}
}
Assuming that MainPage is the page you wanted to show after successful login. I am using Xamarin.Forms with MasterDetailPage to display pages in my example which maybe different from your app but the concept is the same.
Just call DismissViewController (true, null) in your OnAuthenticationCompleted method. Or use the async equivalent:
async void OnAuthenticationCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
Debug.WriteLine("AUTH Completed!");
await DismissViewControllerAsync(true);
if (e.IsAuthenticated)
{
}
}