Ionic NTLM Authentication - IIS - ios

I am building an iOS mobile application using the Ionic framework. The app will be accessing APIs that will be served by an ASP.NET 5 (MVC 6) application hosted on IIS using Integrated Windows Authentication. The server already has a web interface to it that uses an AngularJS client. I have been trying to get a $http call to the server from within an Ionic/Angularjs controller and have had no luck getting through the IIS Integrated windows authentication (I have tried running on the device/simulator as well as ionic serve). I always get a 401 Unauthorized error. I have tried setting withCredentials to true and passing in a username/password in the request with no luck. When I try to access the API URL from safari on an iPhone (a non-windows environment), I do get the Browser Authentication popup which successfully logs me in on entering my intranet windows username password.
I initially had some CORS issues that I have sorted through by adding the CORS service on the server side and also allowing all origins. I also have the proxy setup to avoid CORS issue when testing using ionic serve. Has anyone done something like this before? This is my controller code:
angular.module('starter.controllers', [])
.controller('AppCtrl', function($scope, $ionicModal, $http) {
$http.defaults.useXDomain = true;
$http.defaults.withCredentials = true;
// Form data for the login modal
$scope.loginData = {};
// Create the login modal that we will use later
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: $scope
}).then(function(modal) {
$scope.modal = modal;
});
// Triggered in the login modal to close it
$scope.closeLogin = function() {
$scope.modal.hide();
};
// Open the login modal
$scope.login = function() {
$scope.modal.show();
};
// Perform the login action when the user submits the login form
$scope.doLogin = function() {
console.log('Doing login', $scope.loginData);
$http.post('http://localhost:8100/api/APIAccount/Login',{withCredentials:true})
.then(function(response)
{
console.log('success');
}, function(error) {
console.log('error');
});
};
});

After several hours of troubleshooting, it was as simple as setting up ASP.NET 5 CORS service to allow credentials. In my Startup.cs file in the ConfigureServices function I had to put in the following. Hope this helps someone else in the future.
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder.WithOrigins("http://<domainname>")
.AllowCredentials());
});

Related

Getting 401 Unauthorized with MVC Pages while Identity Razor pages work as expected

Background
I am doing a POC to find out if Angular, Razor and MVC pages work seamlessly in a web application. I started with Visual Studio template named "ASP.NET Core with Angular". I have selected "Individual Accounts" to include default authentication functionality. This creates an Angular app with a secure web API endpoint (WeatherForecast) and provides basic user registration, login, logout, user profile pages etc features built in. So far all works well, when I try to fetch data from the protected API (WeatherForecast) I get redirected to the Identiy/Account/Login razor page where I can login and then get redirected back to Angular and I can see that data is returned and grid is populated. Till this point everything works fine.
The Problem
I added a DemoController class with a basic "Hello World" HTML view. When I try to access this new page with /demo, it works as expected. However, when I apply [Authorize] attribute to the controller, I get 401 Unauthorized. I checked on server side that User.IsAuthenticated property is set to false despite having successfully logged in before. Now interesting observation is that the user profile page (which is protected and works only if there an active login) works fine.
Please note that all API calls issues from Angular use JWT bearer token and work fine. When I try to access user profile page, it does NOT use JWT, it uses cookies to authenticate. The GET request to /demo page also has all these cookies in headers, still it is met with 401.
I spent a lot of time going thru articles, searching web with no success. The closing thing we found is this : ASP.NET Core 5.0 JWT authentication is throws 401 code
But that didn't help either.
The project is created using Visual Studio 2022, .net core 6.0. Here is the Program.cs file for your reference:
using CoreAngular.Data;
using CoreAngular.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();
app.MapFallbackToFile("index.html"); ;
app.Run();
This has been answered here: https://stackoverflow.com/a/62090053/3317709
It turned out that using IdentityServer extension methods add a policy scheme such that only /Identity pages have cookie authentication. The rest default to JWT.
We can customize this by adding our own policy like so:
builder.Services.AddAuthentication()
.AddIdentityServerJwt()
.AddPolicyScheme("ApplicationDefinedAuthentication", null, options =>
{
options.ForwardDefaultSelector = (context) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
context.Request.Path.StartsWithSegments(new PathString("/demo"), StringComparison.OrdinalIgnoreCase))
return IdentityConstants.ApplicationScheme;
else
return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
};
});
// Use own policy scheme instead of default policy scheme that was set in method AddIdentityServerJwt
builder.Services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "ApplicationDefinedAuthentication");

Integrate Graph API toolkit avoiding Login component with existing application authenticated with Azure AD using "#azure/msal-angularjs": "^0.1.1"

I am trying to integrate the React based Graph API toolkit with existing application build on Angular JS 1.x . The React application is deployed as a web component in the existing Angular Application.
The existing Angular application is authenticated with Azure AD using "#azure/msal-angularjs": "^0.1.1" , when I integrate my react application having MGT controls in it, they don't work until and unless I explicitly use the Login Component and after Logging In they react application works.
I want to avoid Login Component from my react application as the users are already logged in to the existing angular application using Azure AD.
I did try to use the Simple Provider but no luck. Can someone please share some sample code and provide the right approach to achieve the same. Below the authentication code of the existing Angular Application.
window.msalApplicationConfig = {
scopes: ['ChannelMessage.Send','Chat.Read','Chat.ReadWrite','Directory.Read.All','email','Group.Read.All','GroupMember.Read.All','openid','profile','Sites.Manage.All','Sites.Read.All','Sites.ReadWrite.All','Team.ReadBasic.All','TeamSettings.Read.All','TeamSettings.ReadWrite.All','User.Read','User.Read.All','User.ReadBasic.All','User.ReadWrite.All']
};
msalAuthenticationServiceProvider.init({
clientID: appConfig.authContextConfig.clientId,
redirectUri: window.location.origin + '/#/',
authority: appConfig.authContextConfig.instance + appConfig.authContextConfig.tenant,
postlogoutRedirectUri: window.location.origin + '/' + appConfig.authContextConfig.postLogoutRedirectUri
});
function msalLoginSuccess(config, defer) {
return function (loginToken) {
msalAuthenticationService.acquireTokenSilent(window.msalApplicationConfig.scopes).then(function (accessToken) {
localStorage.setItem('msal_token', accessToken);
config.extraHeaders[appService.getAuthorizationHeaderName()] = 'Bearer ' + loginToken;
ajaxService.setGlobalHeader(appService.getAuthorizationHeaderName(), config.extraHeaders[appService.getAuthorizationHeaderName()]);
defer.resolve();
});
}
}
Simple Provider Code
Providers.globalProvider = new SimpleProvider((scopes:string[]) => {
//return accessToken for scopes using your msal instance
return Promise.resolve(localStorage.getItem('msal_token'));
});
Providers.onProviderUpdated(()=>{
if(Providers.globalProvider.state==ProviderState.SignedIn)
{
console.log("Signed IN");
setSignedIn(true)
}
Since you are handling authentication, you need to handle state changes of the SimpleProvider when the login state changes. To do this, use the setState setter with the appropriate ProviderState.
Providers.globalProvider.setState(ProviderState.SignedIn)
The components listen for this state and only call the graph when the provider is signed in.

Exception on cancelled external login

(Don't be afraid of this big description, I just tried to be specific, so that it becomes easier for the answerers)
I'm building a web application using ASP.Net Core 2.1 having external login in it. But internal server error occurred when external (facebook) login is canceled and facebook redirects to the source application.
That means, you clicked on Facebook external login button and then canceled it by clicking on "Not Now" button. Facebook redirects back to your application (https://localhost:port/signin-facebook?...); and then voila -- exception.
An unhandled exception occurred while processing the request.
Exception: access_denied;Description=Permissions error
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler.HandleRequestAsync()
When facebook authentication is getting prepared by the Asp.net Core system from Startup.cs class, 'https://.../signin-facebook' route will be generated automatically by the Facebook authentication provider, as described in the Microsoft docs and Github/aspnet:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins?view=aspnetcore-2.1&tabs=aspnetcore2x#create-the-app-in-facebook
https://github.com/aspnet/Security/issues/1756#issuecomment-388855389
If I hit "https://localhost:port/signin-facebook" directly without any query-string, it shows this exception: The OAuth state was missing or invalid.
But expected behavior is - it will be redirected to the default login page.
Here's the startup.cs snippet:
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
services
.AddAuthentication(o => o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
});
services.AddAuthentication()
.AddFacebook(o =>
{
o.AppId = Configuration.GetValue<string>("Facebook:AppId");
o.AppSecret = Configuration.GetValue<string>("Facebook:AppSecret");
});
I configured a custom callbackpath (as descripted in microsoft doc), but same exception.
So..., what's going on? What was the problem? And what's the solution?
FYI, I'm not accessing DB from the application and using default IdentityDbContext with .UseModel() and cookie authentication using HttpContext.SigninAsync. Everything's fine when external login is completed instead of canceling.
According to the Asp.Net Core Github repo, Asp.net Core team is working on it now. Currently developers are using OnRemoteFailure event to handle the exception gracefully and performing the desired action.
Startup.cs:
services.AddAuthentication()
.AddFacebook(o =>
{
o.AppId = Configuration.GetValue<string>("Facebook:AppId");
o.AppSecret = Configuration.GetValue<string>("Facebook:AppSecret");
o.Events.OnRemoteFailure = (context) =>
{
context.Response.Redirect("/account/login");
context.HandleResponse();
return System.Threading.Tasks.Task.CompletedTask;
};
});

How to unit test google oauth passport in sails js with Mocha

Right now I am trying to test my controllers and I need to access session, I found out that you can login using superagent but my only option for loggin in to the web app is through google oauth, and right now I cannot find any appropriate samples for testing with Mocha. Any help?
It depends on how you implemented your sessions.
In my Sails app, after authentication, I set req.session.authenticated = true, along with cookies, etc. One thing you can do if you're doing something similar is in your /login route, add:
if (process.env.NODE_ENV === 'test') {
req.session.authenticated = true;
// do what you would do next after authentication
} else {
// do normal login procedures
}
Then, in your tests, in a before hook, you can use superagent to make a request to the /login route to authenticate:
describe('MyController', function() {
var agent;
before(function (done) {
agent = require('superagent').agent('YOUR_APP_URL');
// authenticate
agent
.post('/login')
.end(done)
});
// in your tests, use the same agent to make future requests
describe('#someAction', function() {
it('should do something', function(done) {
agent.
.post('someAction')
.end(function (err, res) {
// should work!
});
});
});
});
It's just an idea - you can adapt this approach to however you're checking sessions. This works for my Sails app using Mocha for tests.

SignalR 2.0 - 404 in IIS Virtual Directory

I'm having an issue when deploying a very basic MVC5 app running SignalR 2.0.2. Everything works great in my local development environment when I'm running it with IIS Express. When I deploy to IIS, my js receives a 404 error attempting to connect to SignalR.
More specifically, I'm deploying to an application/virtual directory that is running under my Default Web Site. When I publish directly to Default Web Site, everything works successfully so IIS is not the issue.
GET http://myServer/signalr/negotiate?connectionData=%5B%5D&clientProtocol=1.3&_=1395517687175 404 (Not Found)
I'm assuming the 404 is caused by the missing application name. ie: myServer/MyApp/signalr/negotiate...
I've searched a number of posts and SignalR documentation with no luck regarding IIS and Applications/Virtual Directories and SignalR. Below is snippets of code in my app.
Thanks!
JS:
var connection = $.hubConnection();
var proxy = connection.createHubProxy('TestHub');
connection.start()
.done(function () {
console.log('Now connected, connection ID=' + connection.id + ' using transport=' + connection.transport.name);
})
.fail(function () { console.log('Could not connect'); });
Startup.cs:
app.MapSignalR();
Update
By changing the following JS code I was able to 'fix' the issue. The question is, how proper is this?
//var connection = $.hubConnection();
var connection = $.hubConnection("/MyApp/signalr", { useDefaultPath: false });
Your fix seems reasonable.
{ useDefaultPath: false } simply tells SignalR not to append "/signalr" to the url, so you could also create your connection object like this: var connection = $.hubConnection("/MyApp");
Alternatively, if you want to use JS hub proxies generated at /MyApp/signalr/hubs, you can could connect like this:
var proxy = $.connection.testHub;
// Make sure you always wire up client methods before calling start
proxy.client.myClientMethod = function () { /* ... */ };
$.connection.hub.start()
.done(function () { /* ... */ })
.fail(function () { /* ... */ });
http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-javascript-client#genproxy
A solution which will work in dev, and in IIS hosted as application, virtual directory or root is to configure the hub using the page url as its base. This will mean you won't need to hard code the value and negates configuration change for development and deployed scenarios.
var connection = $.hubConnection(document.location.origin + document.location.pathname);

Resources