I spent several hours reading through many different examples and documentation for setting up a portable area with ASP.NET MVC, with the intent of sharing a common login page with authentication for multiple applications. I got it all together and it works nicely so far, but one thing I'm having trouble with is the use of message bus. I see it is a way of communicating between the Host and Portable components, but I don't see a clear way of how to do this.
For instance; if my Portable login is successful, how do I tell the Host so it can do something (set a cookie, redirect to a specific page, etc.)? Also, if I want to send something to the Portable (like the title or Assembly Version of the Host application) how would I do that? I haven't tried anything yet because I cannot seem to find a complete example.
I got it all figured out. There was a MvcContrib source code archive that I was unable to download since Google Chrome was blocking the .zip file, but I was able to get it using Internet Explorer.
Here are the important bits after adapting it to my application. Hopefully this can help someone. I did my best to format my answer properly, this is my first time posting on StackOverflow:
In the Portable class library
Create a LoginResult class using the ICommandResult interface
public class LoginResult: MvcContrib.PortableAreas.ICommandResult
{
public bool Success { get; set; }
public string Message { get; set; }
public string Username { get; set; }
}
Create a LoginMessage class that also uses the ICommandResult interface with LoginResult. The LoginMessage class has a property for a LoginViewModel I use in my Login.cshtml view (it has Username, Password, and some other additional fields I needed for the view)
public class LoginMessage : ICommandMessage<LoginResult>
{
public LoginResult Result { get; set; }
public LoginViewModel Input { get; set; }
}
In the HttpPost action of the Login controller, create an instance of LoginMessage, passing in the LoginViewModel from the login view, and send it to the Host with MvcContrib.Bus.Send
[HttpPost]
public ActionResult Login(LoginViewModel mdl)
{
// TODO: Do basic auth here first, then send to Host for additional validation
// Create and send message to the Host
var message = new LoginMessage { Input = mdl, Result = new LoginResult() };
MvcContrib.Bus.Send(message);
if (message.Result.Success)
{
// Redirect to defaultUrl set in the Host's web.config
FormsAuthentication.RedirectFromLoginPage(mdl.Username, false);
}
return View("Login", "_Layout", mdl);
}
Note: LoginMessage sets a new empty LoginResult and then waits for Success. The Success is set by the Host (shown below). I do this because certain Host applications have specific additional logic that only apply to that application, so I let the Host do what it needs and return to the Portable to let it know if it passed or failed. Eventually, I will have the basic authentication logic in the Portable first and then let the Host do the extra work, but for the sake of this example I am keeping it simple.
In the Host web application (which has a reference to my Portable dll)
Create a handler for the Portable.LoginMessage so we can read it in the Host. Note that IsValidLogin is where I will eventually do my additional authentication logic to see if the user is valid
public class LoginHandler : MvcContrib.PortableAreas.MessageHandler<Portable.LoginMessage>
{
public override void Handle(Portable.LoginMessage message)
{
if (IsValidLogin(message.Input.Username, message.Input.Password))
{
message.Result.Success = true;
message.Result.Username = message.Input.Username;
}
else
{
message.Result.Message = "Username or Password was incorrect";
}
}
private bool IsValidLogin(string username, string password)
{
// TODO: Replace with actual authentication
return username.Equals("admin") && password.Equals("password");
}
}
In the web.config, set the defaultUrl that the Portable will redirect to in the controller I described earlier, when message.Result.Success is True. You do not need to be using Forms Authentication, the mode can be set to None, but you need to have the defaultUrl for this to work.
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Portable/Login" defaultUrl="~/Home/Index" />
</authentication>
</system.web>
That's it! This was a great exercise and learning experience for me. I am still figuring out the second part of my question where I need to send information to the Portable first (like the application title and assembly version) but I'm thinking I can do pretty much the same thing but in reverse, where I send an ICommandMessage to the Portable when my Host starts up (global.asax).
Related
We have the following architecture:
.NET core 3.1 web application using razor pages, jQuery, etc as the UI (not an angular application)
.NET core 3.1 web api application serving as our api layer
Okta as our identity provider
I have implemented the Okta widget and middleware in the web application. Users can login, and after that happens I’m able to get a ClaimsPrincipal, access all of their scopes, and get to any custom profile data I’ve stored via open id. I'm able to secure views through the [Authorize] decoration. All that is working perfectly.
What I need to do now is implement the security checks on the API side. I’ve spent hours and hours looking at examples and have found many, but I'm either missing something obvious or what I'm doing is unique (and I can't imagine that what I'm doing is that unique). Basically what I need to do is:
Have the web app pass the auth and id tokens to the api
Have the api be able to verify the token and then decipher user information from the id token
This would then allow me to implement the necessary security logic on the API side. Let’s say its the API that returns customer orders - well I need to make sure that the user calling it is either an administrator or is the actual customer (so I don’t return customer data to someone who shouldn’t see it). I have all the role stuff figured out, I just can’t, for the life of me, figure out how to determine who someone is via the token?
Passing the tokens is pretty straightforward, but how would I get the token out of the ClaimsPrincipal object? Or do I need to call the Okta API after the user logs in to specifically get the access and id tokens?
Then of course I'll have to figure out how to get the API side to properly validate and parse the token that is sent.
If anyone could help me get started with this or point me in the right direction for an example, I would be very appreciative. At this point I have read every article on Owin, OpenID, Okta, authorization in .net core I could find.
Thanks to Cameron Tinker's suggestion I was able to get this working. There were a few things that tripped me up, so I'll share them here in case anyone experiences the same.
If you're using Okta, you can do all of this through the Okta middleware package. You can do it just using the c# OpenID library, but the Okta.AspNetCore library will help things along.
First you register the middleware in the web app. Okta has lots of examples of this on their site and its pretty straightforward.
Within your web app you can use this to grab the token (after a user has authenticated of course)
await context.HttpContext?.GetTokenAsync("id_token")
Send that along in your API calls as part of the header using via the standard mechanism:
"Authorization" : "Bearer [token]"
On the Web API side, you use the same Okta.AspNetCore middleware package and can then decorate your controllers with [Authorize] to enforce auth on them. Here is where I got tripped up. If you are not using the default auth server in Okta and have setup a custom one for your application, you need to specific it and the audience in your config:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
})
.AddOktaWebApi(new OktaWebApiOptions()
{
OktaDomain = oktaDomain,
AuthorizationServerId = authServerId,
Audience = clientId
});
services.AddAuthorization();
I had complete forgotten about the audience part - and with the way token validation works, that part is required.
From there, the middleware takes care of populating an ClaimsPrincipal for you, so you can access user information via the ClaimsPrincipal (HttpContext.User). I ended up creating a "CurrentUserService" and pulled it out into its own library so that I can consolidate all my auth handlers there; thereby allowing my web app and web api code to check permissions and retrieve information about the current user in the same way. That code is here if you're interested:
public interface ICurrentUserService
{
public ClaimsPrincipal GetCurrentUser();
public string GetCurrentUserDisplayName();
public string GetCurrentUserFullName();
public string GetCurrentUserId();
public DateTime? GetCurrentUserDob();
public string GetCurrentUserGender();
public AddressFromClaimsDTO GetCurentUserAddress();
public bool IsAuthenticated();
}
public class CurrentUserService : ICurrentUserService
{
private const string FULL_ADDRESS_CLAIM_TYPE = "address";
private readonly IHttpContextAccessor _context;
public CurrentUserService(IHttpContextAccessor context)
{
_context = context;
}
/// <summary>
/// Gets whether or not the current user context is authenticated.
/// </summary>
/// <returns></returns>
public bool IsAuthenticated()
{
return GetCurrentUser().Identity.IsAuthenticated;
}
/// <summary>
/// Gets the current user's address.
/// TODO: tie this into our address data model... but if addresses live in Okta what does that mean?
/// </summary>
/// <returns></returns>
public AddressFromClaimsDTO GetCurentUserAddress()
{
var addressClaim = GetClaim(FULL_ADDRESS_CLAIM_TYPE);
if (addressClaim != null)
{
//var parseValue = addressClaim.Value.ToString().Replace("{address:", "{\"address\":");
var address = JsonSerializer.Deserialize<AddressFromClaimsDTO>(addressClaim.Value.ToString());
return address;
}
else
{
return new AddressFromClaimsDTO();
}
}
public ClaimsPrincipal GetCurrentUser()
{
return _context.HttpContext.User;
}
public string GetCurrentUserDisplayName()
{
return GetCurrentUser().Identity.Name;
}
public string GetCurrentUserFullName()
{
throw new NotImplementedException();
}
public string GetCurrentUserId()
{
throw new NotImplementedException();
}
public DateTime? GetCurrentUserDob()
{
var claim = GetClaim("birthdate");
if (claim != null && !string.IsNullOrEmpty(claim.Value))
{
return DateTime.Parse(claim.Value);
}
else
{
return null;
}
}
public string GetCurrentUserGender()
{
return GetClaim("gender")?.Value.ToString();
}
public Claim GetClaim(string claimType)
{
return _context.HttpContext.User.FindFirst(x => x.Type == claimType);
}
}
Your ID Provider, Okta in this case, will issue an OpenID Connect authorization bearer token that you will need to pass along to any application that you want to protect.
On the Web Api side of your application, you will need to register your middleware for handling processing of Okta's OpenID Connect tokens. Then you can decorate your controllers/actions with [Authorize] and you can check an identity's claims.
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
I'm trying to play around with WebSockets on IIS 8.5. I started off with a couple of very basic C# classes from a lesson:
using Microsoft.Web.WebSockets;
using System.Web;
public class ChatHttpHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
context.AcceptWebSocketRequest(new WebSocketChatHandler());
}
public bool IsReusable
{
get { return true; }
}
}
public class WebSocketChatHandler : WebSocketHandler
{
private static WebSocketCollection clients = new WebSocketCollection();
private string name;
public override void OnOpen()
{
this.name = this.WebSocketContext.QueryString["username"];
clients.Add(this);
clients.Broadcast(string.Format("{0} joined.", name));
}
public override void OnMessage(string message)
{
clients.Broadcast(string.Format("{0}: {1}", name, message));
}
public override void OnClose()
{
clients.Remove(this);
clients.Broadcast(string.Format("{0} left.", name));
}
}
and a simple HTML client. The project builds ok, but when I try to connect to the handler, it returns error 500. The problem is that I cannot see what the exact error is, because neither Chrome nor FF load the response body for ws:// scheme, so i cannot even see it in the Network tab of Developer Tools (though IIS provides the body, as I can see from from the response' Content-Length).
Is there a way to see the response body in this situation? Or what am I missing with WebSockets in IIS?
The problem was with web.config.
I added
<httpRuntime targetFramework="4.5.1" />
to system.web section and it finally began to work
You should be able to see the cause of the error in the Windows Event Viewer.
Fiddler will show you the connection and that it has upgraded to web socket so you can use that tool to at least show you if the connection worked or not. I'm not aware of a tool which can show you the traffic flowing over the socket once it has been upgraded although there might be one.
Better still, debug it in Visual Studio with breakpoints and 'break on exception' set. You can tell VS to use IIS as the server by right clicking the web site and going to Property Pages then Start Options. Tick Use custom server and put your URL into the textbox. Click Specific page and choose your default page.
Comparing it to my working solution using the same DLL, I don't spot any obvious issues with the handling of the socket, so I would suggest commenting out this.name = this.WebSocketContext.QueryString["username"]; for now and replacing it with this.name = "TEST"; as that appears to be about the only code which deviates from the samples. Keep it simple until its working!
I have a solution which includes a thick client (implemented using CefSharp for the majority of the user interface), and the javascript application needs to execute some C# logic in the application hosting the CEF browser. I considered using WebView.RegisterJsObject(), but I can write less glue code if I can just use $.ajax() from the html pages.
I already have ServiceStack set up for the web services and the web client in this solution. I'd like to route requests from the CEF browser to a local ServiceStack host (without actually using http).
Here's some psuedo code to illustrate what I would like to do:
public partial class MainWindow : IRequestHandler {
WebView _webView;
CefSharpServiceStackHost _serviceHost;
public MainWindow() {
// initialize CefSharp...
_webView.RequestHandler = this;
// initialize ServiceStackHost...
}
// other IRequestHandler methods...
// method this intercepts ajax calls from the CEF browser
public bool OnBeforeResourceLoad(IWebBrowser browser, IRequestResponse requestResponse) {
// translate CefSharp.IRequestResponse to ServiceStack.IRequest or HttpRequest
// should execute HelloService.Any() for the requestResponse.Url = "/hello/Zach"
var response = _serviceHost.ExecuteService(Translate(requestResponse));
requestResponse.RespondWith(response.Stream);
return false;
}
}
[Route("/hello/{Name}")]
public class Hello {
public string Hello { get; set; }
}
public class HelloService {
public object Any(Hello request) { // ... }
}
The part I can't figure out is how to extend ServiceStackHost so I can pass some sort of request object to it. Is this even possible?
This might be a stupid answer, but why not just use http anyway? The web is so heavily based on it that things actually gets easier if you use it even in cases like this (where it isn't really necessary).
If this isn't OK, you can implement a custom scheme handler that routes requests to foo://bar to your C# code, and do whatever you like. The CefSharp.Wpf.Example has an example custom scheme handler, so it should help you along the way.
What you're after sounds similar to how MQ Servers execute services in ServiceStack by simply routing messages to:
ServiceController.ExecuteMessage(IMessage)
There are a number of other API's on ServiceController you can use to execute requests in ServiceStack:
//Execute the Request DTO with an empty Request context:
object Execute(object requestDto)
//Execute the Request DTO with the supplied Request context:
object Execute(object requestDto, IRequest request)
For the IRequest context, you can use the built-in BasicRequest class, or your own that implements IRequest.
For reasons I would rather not discuss, I need to create a custom authentication system for my app. I was just reviewing the system and am having some doubts if my solution is thread safe. My goal was to create a solution that would allow my app to authenticate a user one time and that users authentication info would be shared by all master pages, pages, classes, user controls, etc that are used. (But not share the same info between users)
Here is my setup:
PageHttpModule.cs - this is added to the web.config as a httpModule.
public class PageHttpModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.AuthenticateRequest += new EventHandler(OnAuthenticateRequest);
}
public void OnAuthenticateRequest(Object s, EventArgs e)
{
CurrentUser.Initialize();
}
public void Dispose() { }
}
CurrentUser.cs
public static class CurrentUser
{
public static bool IsAuthenticated { get; private set; }
public static string Email {get; set;}
public static string RealName {get; set;
public static string UserId {get; set;}
public static void Initialize()
{
CurrentUser.AuthenticateUser();
}
Note: this is a scaled down version of my authentication code.
public static void AuthenticateUser()
{
UserAuthentication user = new UserAuthentication();
user.AuthenticateUser();
if (user.IsAuthenticated)
{
CurrentUser.IsAuthenticated = true;
CurrentUser.UserId = user.UserId;
CurrentUser.Email = user.Email;
CurrentUser.RealName = user.RealName;
}
}
}
UserAuthentication.cs
public class UserAuthentication
{
public string Email { get; set; }
public string RealName { get; set; }
public string UserId { get; set; }
public bool IsAuthenticated { get; private set; }
public UserAuthentication()
{
IsAuthenticated = false;
Email = String.Empty;
RealName = String.Empty;
UserId = String.Empty;
}
public void AuthenticateUser()
{
//do some logic here.. if the user is ok then
IsAuthenticated = true
Email = address from db
UserId = userid from db;
Realname = name from db;
}
}
I have tested between 3 different browsers and it seems to work fine, but I am still learning and don't want to make a huge mistake.
If my logic is totally wrong, then how should I do it so I dont have to put user lookups on every page directly?
No, this is not thread-safe. For instances of the application living in separate processes or AppDomains, this will be just fine. But if your ASP.NET server is going to serve multiple requests at once using threading, you are going to have some very bad side effects if two people try to use the application at the same time.
In the Init method, the HttpApplication parameter is described as:
An HttpApplication that provides access to the methods, properties, and events common to all application objects within an ASP.NET application
The key here is that there is one PageHttpModule for the lifetime of the app, and all static objects that exist in the lifetime of the app will share those variables.
BUT... the lifetime of CurrentUser is only within the scope of the OnAuthenticateRequest event, unless some other reference keeps the object alive. If it were a PageHttpModule member-level variable, you'd have issues that you would have noticed immediately. In your situation, however, you'll work fine so long as you don't get more than one simultaneously-processed OnAuthenticateRequest call.
The answer to your question is no, you're not guaranteed to be thread-safe. If two authentication requests come in simultaneously, you're not guaranteed to have one event complete before the other begins, in which case the second user can appear authenticated, when it's really the first user that was logged on.
Update
I think part of the problem is coming from a misunderstanding of AuthenticateRequest... By the time this event is called, the user has already been authenticated by either Windows or Forms authentication... you're just getting notified that it's happened. In fact, the property User.Identity.IsAuthenticated has already been set (I believe this event fires even if the user fails authentication, but I won't swear to that without double-checking).
If I understand what you are after, you're really trying to write your own custom membership provider. If you take this approach, you will have all the benefits of the built-in authentication... all of the standard properties related to authentication will be set and accessible, and will be isolated to a user's session in the manner you want.
Writing a custom provider is not a small feat, but it is doable, and you should be able to reuse a lot of the logic and code you're currently using for your classes.
Trying to completely re-write the authentication mechanism would be jumping through painful, complicated hoops.
Some links:
http://www.devx.com/asp/Article/29256/0/page/3
http://www.codeproject.com/KB/aspnet/WSSecurityProvider.aspx
http://msdn.microsoft.com/en-us/library/f1kyba5e%28v=VS.90%29.aspx
The properties you must implement may look daunting, but unless you need a specific functionality (such as ResetPassword), you can simply throw a NotImplementedException. Code only what you'll use.
Why not just do it the way microsoft recommends?
http://msdn.microsoft.com/en-us/library/9wff0kyh.aspx
I've done custom authentication this way and it works fine.
Here is another link which should prove useful:
Link
What you have done with IHttpModule seems like a good path to tackle this kind of issue. One of the purposes of the http module as stated by microsoft is to enable for any kind of special authentication. When http module intializes it uses the same instance for new requests. Since you dont have any global variables I am not so sure how to address your thread safe question. It seems like you are onlu reading some data out, so please elaborate!