Use SignalR to notify connected clients of new login to Web.API - oauth-2.0

I have an exiting set of APIs developed with ASP.NET Web API 2. Currently users need to authenticate before using most of the APIs (a few are public). Here is my scaled down OAuthProvider.
public class MyOAuthProvider : OAuthAuthorizationServerProvider
{
// Validation of user name and password takes place here
// Code not show for brevity
// If user is validated issue then issue them a JWT token
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
In separate application I have experimented with SignalR and have a Hub class that takes the connectionId and userName of a user and stores it to an in-memory Dictionary named connectionDictionary.
public class ChatHub : Hub
{
public static Dictionary<string,string> connectionDictionary = new Dictionary<string, string>();
public override Task OnConnected()
{
connectionDictionary.Add(Context.ConnectionId, Context.User.Identity.Name);
return base.OnConnected();
}
}
How do I go about joining these together so that when a user authenticates with the API, their connectionId and userName are stored in the Dictionary? The idea being that once a user has logged in I will notify all other connected clients.

Related

How to allow basic authentication on a single WebAPI endpoint in an MVC 5 app employing OpenID (ADFS)

I have a web application, hosted on our organization's web servers, which uses OpenID Connect (OIDC) in the shape of Active Directory Federated Services (ADFS) for authentication. It previously used forms authentication, but our organization is migrating all web apps to ADFS for improved security. The web app in general works as expected.
However, the app, written in MVC 5 targeting .NET Framework 4.7.2, also has a single WebAPI endpoint which is used for allowing users to subscribe, in Outlook, to an iCalendar feed exposed by the application (it's an appointment booking system). Before implementing ADFS, I made this work by using a custom AuthorizationFilterAttribute on the WebAPI endpoint which required username and password in the request header - basic authentication, in other words. It's far from ideal, but as far as I can tell it's the only kind of authentication Outlook supports.
Since implementing ADFS, though, the endpoint appears to be no longer accessible by Outlook (crucially, I'm not prompted for credentials when I add the calendar subscription). Adding it in the full Outlook app fails with no errors or feedback and doing so in the web client (an idea prompted by this article)
appears to work but again, I'm not prompted for credentials and no events ever appear in the calendar.
When I call it in a web browser I am redirected to the ADFS login page. When I call it in Postman with no Authorization header I similarly get the ADFS login page in response, but if I specify Basic Auth with the request and pass in my username and password, I get the correct response with the iCal feed.
As you can see below in my HandleUnathorized method I'm adding a WWW-Authenticate header which, as I understood it, was supposed to cause the client to prompt for login credentials when it received a 401 response, but this isn't happening.
My WebAPI controller looks like this:
namespace AppointmentBooking.Web.API.Controllers
{
public class AppointmentController : ApiController
{
[CustomBasicAuthentication] // Use a custom authentication filter for this method to support Outlook
[HttpGet]
[Route("ical/{username}")]
public HttpResponseMessage GetStaffICalendar(string username)
{
if (HttpContext.Current.User.Identity.IsAuthenticated == true)
{
string currentUsername = HttpContext.Current.User.Identity.Name;
if (username == currentUsername)
{
string calendarString = ICalendarService.GetICalendarForHost(currentUsername);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(calendarString, new UTF8Encoding(false), "text/calendar");
return response;
}
return Request.CreateResponse(HttpStatusCode.Unauthorized, "You may only open your own iCalendar, not that of another user");
}
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
My custom AuthorizationFilterAttribute is defined as follows:
namespace AppointmentBooking.Web.Api.Filters
{
public class CustomBasicAuthentication : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = Membership.ValidateUser(userName, password);
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
actionContext.RequestContext.Principal = principal;
Thread.CurrentPrincipal = principal;
System.Web.HttpContext.Current.User = principal;
return;
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' realm='Access your iCalendar");
}
}
}
And, in case it's helpful, the app's WebApiConfig.cs file is like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// WebAPI when dealing with JSON & JavaScript!
// Setup json serialization to serialize classes to camel (std. Json format)
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
}
How can I fix this endpoint so that it causes clients (particularly Outlook) to prompt for login credentials, rather than redirecting to the ADFS login page when I call it?

How do I convert an external OAuth Identity into a local identity in Umbraco?

I am attempting to develop a proof-of-concept using my company's website as an OAuth authorization server to be consumed by Umbraco via OWIN/Katana. All of the OAuth plumbing appears to be working just fine but Umbraco isn't converting the external identity into a local identity. Instead of being logged into the Umbraco backend, the user lands back on the login page. The only change once the OAuth flow has completed is that Umbraco has created an UMB_EXTLOGIN cookie containing a long encrypted string.
If I login using a local identity directly (i.e. user name and password on the Umbraco backend login page) Umbraco creates 4 cookies: UMB_UCONTEXT, UMB_UPDCHK, XSRF-TOKEN and XSRF-V. I assume I'm missing something that converts the external identity into a local one, but I'm not sure what that is.
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.ConfigureBackOfficeMyCompanyAuth(Properties.Settings.Default.ClientId, Properties.Settings.Default.ClientSecret);
}
}
UmbracoMyCompanyAuthExtensions.cs
public static class UmbracoMyCompanyAuthExtensions
{
public static void ConfigureBackOfficeMyCompanyAuth(this IAppBuilder app, string clientId, string clientSecret,
string caption = "My Company", string style = "btn-mycompany", string icon = "fa-rebel")
{
var options = new MyCompanyAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
Provider = new MyCompanyAuthenticationProvider(),
CallbackPath = new PathString("/MyCompanySignIn")
};
options.ForUmbracoBackOffice(style, icon);
options.Caption = caption;
app.UseMyCompanyAuthentication(options);
}
}
MyCompanyAuthenticationExtension.cs
public static class MyCompanyAuthenticationExtensions
{
public static IAppBuilder UseMyCompanyAuthentication(this IAppBuilder app, MyCompanyAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(MyCompanyAuthenticationMiddleware), new object[] { app, options });
return app;
}
public static IAppBuilder UseMyCompanyAuthentication(this IAppBuilder app, string clientId, string clientSecret)
{
MyCompanyAuthenticationOptions options = new MyCompanyAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret
};
return app.UseMyCompanyAuthentication(options);
}
}
My custom implementation of AuthenticationHandler<T>.AuthenticateCoreAsync() returns an AuthenticationTicket with the following claims and properties.
Claims
GivenName = My First Name
FamilyName = My Last Name
Name = My Full Name
Email = My Email Address
Properties
.redirect = /umbraco/
Dont have any code ready at hand but from past experiences, using Facebook OAuth, you will have to wire in your own logic to basically either or both, convert you OAuth object (user) into an umbraco one.
When we done it previously the first time a user does it (checking by email), it creates a new user then every subsequent login get the umbraco user by their email and log them in in code. This was the same for both backend users and front end members.
So after much wheel spinning, I finally figured it out. The resulting ClaimsIdentity didn't contain a NameIdentifier claim. I had my OAuth middleware include that claim using the email address as the value and it started working.
FYI, if you're looking to autolink external and local accounts upon external login, here's a really good example that worked for me.

How to extend the Owin OAuth to accommodate the Application level login confirmation

I am currently working with MVC5 application which is using Owin OAuth authentication. I am looking forward to extend the login criteria where I have added couple of tables (Application table (ApplicationId(guid), ApplicationName(nvarchar) and ApplicationUserTable(id, ApplicationId(FK from Application table), UserId(FK column from ASPNetUsers table))) in my security DB.
Could please anyone give me some idea on how to access this ApplicationUserTable in Owin context so that I can verify first if user belong to a particular application? I have looked through quite a few examples but didn't find anything relevant to the particular scenario i am working with.
You could set your ApplicationId field as the ClientId, then you will have a different ClientId for each application.
When the user sends an authentication token request, in the GrantResourceOwnerCredentials method when you check the user credentials, check if the user belongs to the application that received ClientId represents.
In a simple way, it could be something like the following:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
...
var user = await _userService.GetUserAsync(context.UserName, context.Password, context.ClientId);
if (user == null)
{
context.SetError("invalid_grant", "The user name, password or clientId is incorrect.");
return;
}
...
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
And in the user service:
public async Task<User> GetUserAsync(string userName, string password, string clientId = null)
{
var user = await _userManager.FindAsync(userName, password);
if (user == null || (clientId != null && user.ApplicationUsers.Where(au => au.ApplicationId == clientId).Count() == 0))
{
return null;
}
return user;
}
In the ValidateClientAuthentication method you should validate the ClientId-Secret are in your Application table.
I hope it helps.

Facebook Login with Spring Social using Existing User Access Token

Here's what I currently have:
Spring REST service where many of the APIs require the user to be authenticated
A 'registration' API (/api/v1/register)
A 'login' API that takes username/password (/api/v1/login)
'Facebook Login' API that relies on Spring Social and Spring Security to create a User Connection and log my user in (/auth/facebook)
My problem is that I want these APIs to be used by multiple clients, but the way Facebook Login is right now, it doesn't work well on mobile (works great on a website).
Here's the mobile scenario:
I use Facebook's iOS SDK to request permission from the user
Facebook returns a user access token
I want to send my backend service this token and have Spring Social accept it, create the User Connection, etc.
Can this be done? Or am I going to have to write my own API to persist the User Connection?
Appreciate any help!
I had the exact same issue and here's how I made it work. You probably have a SocialConfigurer somewhere with the following:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Bean
public FacebookConnectionFactory facebookConnectionFactory() {
FacebookConnectionFactory facebookConnectionFactory = new FacebookConnectionFactory("AppID", "AppSecret");
facebookConnectionFactory.setScope("email");
return facebookConnectionFactory;
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(facebookConnectionFactory());
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
// Other #Bean maybe ...
}
From here, what you can do is, in a Controller/RestController, add a mapping with a RequestParam for your token that you will send to your server:
#Autowired
private FacebookConnectionFactory facebookConnectionFactory;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#RequestMapping(value = "/my-facebook-url", method = RequestMethod.POST)
public String fb(#RequestParam String token) {
AccessGrant accessGrant = new AccessGrant(token);
Connection<Facebook> connection = facebookConnectionFactory.createConnection(accessGrant);
UserProfile userProfile = connection.fetchUserProfile();
usersConnectionRepository.createConnectionRepository(userProfile.getEmail()).addConnection(connection);
// ...
return "Done";
}
Useful references
UsersConnectionRepository
ConnectionRepository

How can I synchronize access to a private static field of a class by its methods?

I am implementing COMET in my MVC web application by using the PokiIn library for pushing notifications to clients.
Whenever a client connects, the ClientId is available in the OnClientConnected event of the CometWorker class:
public static Dictionary<int, string> clientsList
= new Dictionary<int, string>();
public static string clientId = "";
static void OnClientConnected(string clientId,
ref Dictionary<string, object> list)
{
BaseController.clientId = clientId;
}
I assign the the clientId received in the handler to the static ClientId of controller. And then when the Handler action is called, I map this ClientId to the Identity of the logged in user:-
public ActionResult Handler()
{
if (User.Identity.IsAuthenticated)
{
if (clientsList.Keys.Contains(currentUser.UserId))
clientsList[currentUser.UserId] = clientId;
else
clientsList.Add(currentUser.UserId, clientId);
}
return View();
}
Because multiple requests will be served by different threads on the server, each will access the static ClientId in both the methods.
How can I synchronize its access, so that untill one request is done with it in both the methods (OnClientConnected and Handler), the other request waits for it ?
Please tell me if my question is not clear. I will try to improve it further.
Store the clientid in the user's session not in a static variable on the controller. It needs to be in data associated with the user not the entire application. Or better yet, resolve the name/id lookup when the client connects.
I think you should use lock(clientsList){} whenever you want to update your dictionary

Resources