Ok I should definitely enhance my search skill, just did a quick search on the open issues, missed the closed ones.... :(
Thanks!
I was a little bit faster ;)
That is exactly what I did, although it is not a really polished approach I took. One thing that bothered me was when I was testing with the IIS settings:
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = true;
});
According to the documentation "iis.AutomaticAuthentication = true" should populate the "HttpContext.User" without the explicit need of an "[Authorize]" attribute which would be perfect to avoid the 401 but during my tests it never worked.
Ok I found a 'hacky' solution by creating my own ExternalAuthenticationSource
Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<WinAuthSource>();
public class WinAuthSource : DefaultExternalAuthenticationSource<Tenant, User>, ITransientDependency
{
public override string Name
{
get { return "Windows Authentication"; }
}
public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
{
if (plainPassword == AppConsts.WinAuthSecret)
{
return Task.FromResult(true);
} else
{
return Task.FromResult(false);
}
}
}
And adding a new Method to the TokenAuthController:
[HttpPost]
[Authorize]
public async Task<AuthenticateResultModel> WinAuthenticate()
{
if (User.Identity.IsAuthenticated && User is System.Security.Principal.WindowsPrincipal)
{
var userName = User.Identity.Name.Split('\\').Last();
var usr = await _userManager.FindByNameAsync(userName);
if (usr != null)
{
var loginResult = await _logInManager.LoginAsync(userName, AppConsts.WinAuthSecret, GetTenancyNameOrNull());
if (loginResult.Result == AbpLoginResultType.Success)
{
//Login!
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new AuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
TwoFactorRememberClientToken = null,
UserId = loginResult.User.Id,
ReturnUrl = null
};
}
}
}
return null;
}
So basically WinAuthSource authenticates every request with a given secret that I inject in case a valid Windows Authentication has been found. Not really nice but it works.
If I use a browser that has no support for Integrated Authentication a login prompt appears and if this gets canceled a 401 error is triggered because of the required authorization on the action. Is there a way to prevent the built-in error modal popup just for this case? Where is the configuration for the global error handling in Angular?
I am able to find the existing user based on the infos provided by Windows Authentication with the UserManager but I cannot find a way to do some kind of application login (so that AbpSession gets set, access tokens get generated etc.)
Do I have to add Windows Authentication as External Login Provider and if so how? Is there a way to extend the LogInManager so I can pass in an existing user or the Windows Authentication Principal and perform a valid application login?
Thanks for all the replies. I spent quite some time testing and trying to get things to work (and I am still a bit astonished that this is not a more frequent request, I can't be the first customer with such a request).
I still need help to getting the right dots connected.
What I have done so far:
var authenticationBuilder = services.AddAuthentication(opt => {
opt.DefaultScheme = IISDefaults.AuthenticationScheme;
opt.DefaultAuthenticateScheme = IISDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
});
With this configuration I was able to get Windows Authentication working even from Angular. I added a test button to the login page that fires:
getUser(): Observable<string> {
console.log('Calling getUser');
let serviceUrl: string = `${AppConsts.remoteServiceBaseUrl}/Home/GetUser`;
return this.http.get(serviceUrl, { responseType: 'text' })
.map((rslt: string) => {
return rslt;
});
}
Using an Http Interceptor adding "withCredentials" and targeting test action "/Home/GetUser" that simply returns
return Ok($"Authenticated: {User.Identity.Name}");
I then even added the [Authorize] attribute to "http://localhost:22742/api/services/app/Session/GetCurrentLoginInformations" for testing which also worked.
But the question now is: how do I now get a valid AbpSession that the application can use? How can a achieve an application side login with the information from Windows Authentication and where is the best place to initiate this? My guess would be the mentioned "GetCurrentLoginInformations" but how can I achieve a login there?
Any help is really appreciated.
Thanks for the reply.
Yes the server side is not so much an issue as the client - speak Angular - side. How can I get Angular to see/use these credentials? From the information in the second link I see that the addition of "{ credentials: 'include' }" to the http calls could be the answer but is there some kind of configuration to enables this application wide? Because I think it would be quite a task the chase down every http call in the client application (especially the parts not using the default API through service-proxies, like the call to '/AbpUserConfiguration/GetAll' at the start of the application etc.) I also once thought about creating an http interceptor to inject exactly this option ("{ credentials: 'include' }") but then saw that the template is not using the the new httpclient from '@angular/common/http' but the old '@angular/http' so this is also not an option.
So how can I access the Windows Authentication information from within Angular to create a 'standard' Abp user session?