-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
CefSharp Not Prompting for Windows Authentication Credentials (NTLM/Negotiate) – Authentication Fails Silently #5166
Description
Is there an existing issue for this?
- I have searched both open/closed issues, no issue already exists.
CefSharp Version
139.0.280
Operating System
Windows 10
Architecture
x64
.Net Version
.Net 4.8
Implementation
WPF
Reproduction Steps
The target website requires Windows Authentication (NTLM or Negotiate scheme). When I access the site in a standard Chrome browser, it correctly prompts for credentials, and authentication succeeds after entering them. However, in my CefSharp application, no prompt appears, and the authentication fails silently, resulting in an unauthorized response (e.g., 401 error).
Expected behavior
CefSharp should either automatically handle Windows Authentication using the current user's credentials or prompt for credentials if needed, similar to Chrome.
Actual behavior
- No credential prompt is shown.
- Authentication fails without any user interaction.
- The site loads with an error indicating unauthorized access.
I've overridden GetAuthCredentials in my RequestHandler to return true for NTLM/Negotiate schemes. I've also tried setting preferences like --auth-server-whitelist and --auth-negotiate-delegate-whitelist via RequestContextSettings, but nothing triggers the prompt.
Regression?
No response
Known Workarounds
No response
Does this problem also occur in the CEF Sample Application
Yes using WPF/OffScreen command line args
Other information
What I've Tried:
Overriding RequestHandler.GetAuthCredentials to return true for NTLM/Negotiate (as shown in code below).
Setting command-line args via CEF initialization (e.g., --auth-server-whitelist="example.com", --auth-negotiate-delegate-whitelist="example.com").
Creating a custom RequestContext with preferences for auth whitelists (as in the code).
Handling certificate errors in OnCertificateError to trust self-signed certs if relevant (though this isn't the core issue).
Disabling cache and ensuring no session persistence interferes.
Testing with different CefSharp versions and CEF builds.
Searching extensively, including:
- CefSharp Forum: NTLM Authentication
- CefSharp Forum: Windows Authentication
- GitHub Issue: CefSharp #1711
- Bitbucket PR: CEF Pull Request #63
- Chromium Embedded Issue #1150
- Stack Overflow: SSO with CefSharp
- GitHub Discussion: CefSharp #3648
I've scoured the internet for similar issues, but most solutions revolve around the above approaches, which don't work here.
The site is hosted internally with IIS using Windows Authentication. My app runs on Windows 10/11, and the user is domain-joined.
Relevant Code:
Here's my custom RequestHandler where I handle auth and certs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using BMAura.WebBrowser.Utilities;
using CefSharp;
using Serilog;
namespace BMAura.WebBrowser.Handlers
{
public class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
protected override bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
return scheme.Equals("ntlm", StringComparison.OrdinalIgnoreCase) ||
scheme.Equals("negotiate", StringComparison.OrdinalIgnoreCase);
}
protected override bool OnCertificateError(IWebBrowser browserControl, IBrowser browser,
CefErrorCode errorCode, string requestUrl,
ISslInfo sslInfo, IRequestCallback callback)
{
Log.Warning(sslInfo.CertStatus.ToString());
Log.Warning(sslInfo.X509Certificate.Issuer);
if (!CertIsTrustedEvenIfInvalid(sslInfo.X509Certificate))
return base.OnCertificateError(browserControl, browser, errorCode, requestUrl,
sslInfo, callback);
Log.Warning("Trusting: " + sslInfo.X509Certificate.Issuer);
if (callback.IsDisposed) return true;
using (callback)
{
callback?.Continue(true);
}
return true;
}
private bool CertIsTrustedEvenIfInvalid(X509Certificate certificate)
{
var debug = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase)
{
["cn"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "some", "data" },
["ou"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "other", "stuff" },
["o"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "..." },
["l"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Atlantis" },
["s"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Outer Space" },
["c"] = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "whatsnot" },
};
var x509issuer = certificate.Issuer
.Split(",".ToCharArray())
.Select(part => part.Trim().Split("=".ToCharArray(), 2).Select(p => p.Trim()))
.ToDictionary(t => t.First(), t => t.Last());
return x509issuer.All(kvp => debug.ContainsKey(kvp.Key) &&
debug[kvp.Key].Contains(kvp.Value));
}
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture,
bool isRedirect)
{
if (string.IsNullOrEmpty(frame.Url) ||
CommandLineArgumentsHelper.BrowserMode == BrowserMode.Admin ||
CommandLineArgumentsHelper.BrowserMode == BrowserMode.Market)
return false;
if (request.Url.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".doc", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".docx", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) ||
request.Url.EndsWith(".avi", StringComparison.OrdinalIgnoreCase))
{
Log.Information("Открыт медиафайл. URL: {Url}",
request.Url);
}
var urlCollection = GetUrlsFromArgs() ??
ConfigurationManager.WebBrowserConfiguration.UrlCollection.CurrentValue ??
new ObservableCollection<string>();
if (urlCollection.Any(f => request.Url.StartsWith(f)))
{
return false;
}
return !request.Url.EndsWith(".pda") && !request.Url.StartsWith("chrome-extension");
}
private ObservableCollection<string> GetUrlsFromArgs()
{
return string.IsNullOrEmpty(CommandLineArgumentsHelper.TargetUrl) ? null :
new ObservableCollection<string>(new string[] { CommandLineArgumentsHelper.TargetUrl });
}
protected override bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl,
WindowOpenDisposition targetDisposition, bool userGesture)
{
return true;
}
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame,
IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
return new CustomResourceRequestHandler();
}
}
}
And here's how I initialize the browser in my ViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Security.Principal;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using BMAura.WebBrowser.Handlers;
using BMAura.WebBrowser.Utilities;
using CefSharp;
using CefSharp.DevTools.Network;
using CefSharp.Wpf;
using Serilog;
namespace BMAura.WebBrowser.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
public IBrowser Browser { get; private set; }
private readonly BaseInactivityHelper _inactivity = new BaseInactivityHelper(10);
private string _startAddress;
private string _address;
public string Address
{
get => _address;
set => SetAndRaise(ref _address, value);
}
private bool _isExitButtonVisible;
public bool IsExitButtonVisible
{
get => _isExitButtonVisible;
set => SetAndRaise(ref _isExitButtonVisible, value);
}
private bool _hideInterface;
public bool HideInterface
{
get => _hideInterface;
set => SetAndRaise(ref _hideInterface, value);
}
private ObservableCollection<string> _urlCollection;
public ObservableCollection<string> UrlCollection
{
get => _urlCollection;
set => SetAndRaise(ref _urlCollection, value);
}
private Visibility _startPageVisibility = Visibility.Collapsed;
public Visibility StartPageVisibility
{
get => _startPageVisibility;
set => SetAndRaise(ref _startPageVisibility, value);
}
private Visibility _downloadHintVisibility = Visibility.Collapsed;
public Visibility DownloadHintVisibility
{
get => _downloadHintVisibility;
set => SetAndRaise(ref _downloadHintVisibility, value);
}
public MainWindowViewModel()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File("logs/weblog-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
UrlCollection = GetUrlsFromArgs() ??
ConfigurationManager.WebBrowserConfiguration.UrlCollection.CurrentValue ??
new ObservableCollection<string>();
_inactivity.OnInactivity += _inactivity_OnInactivity;
if (UrlCollection.Count > 1)
StartPageVisibility = Visibility.Visible;
if (UrlCollection.Count == 1)
Address = UrlCollection[0];
//IsExitButtonVisible = CommandLineArgumentsHelper.BrowserMode != BrowserMode.Kiosk;
IsExitButtonVisible = true;
HideInterface = CommandLineArgumentsHelper.HideInterface ?? false;
}
private void _inactivity_OnInactivity(int inactivityTime)
{
Address = _startAddress;
Log.Information("Переход на стартовую страницу: {Address}, страртовый адресс {_startAddress}", Address,
_startAddress);
}
private ObservableCollection<string> GetUrlsFromArgs()
{
return string.IsNullOrEmpty(CommandLineArgumentsHelper.TargetUrl)
? null
: new ObservableCollection<string>(new string[] { CommandLineArgumentsHelper.TargetUrl });
}
private ICommand _initializeBrowserCommand;
public ICommand InitializeBrowserCommand => _initializeBrowserCommand ??= new RelayCommand(obj =>
{
if (!(obj is ChromiumBrowser browser))
return;
browser.IsBrowserInitializedChanged += (sender, e) =>
{
if (!(e.NewValue is bool isInitialized) ||
!isInitialized)
return;
Browser = browser.GetBrowser();
browser.RequestContext = CreateNewRequestContext("user_data", new Uri(Address).Host);
browser.DownloadHandler = new CustomDownloadHandler();
browser.RequestHandler = new CustomRequestHandler();
browser.LifeSpanHandler = new CustomLifeSpanHandler();
browser.MenuHandler = new CustomContextMenuHandler();
browser.JavascriptObjectRepository.Register("Logger", new WebLogger(), isAsync: true);
browser.ShowDevTools();
if (string.IsNullOrEmpty(_startAddress))
{
_startAddress = Address;
Log.Information("Базовый адрес: " + _startAddress);
}
Log.Information(
"Пользователь выполнил вход в систему. URL: {Url}, Пользователь: {User}",
Address, WindowsIdentity.GetCurrent()?.Name);
};
CustomDownloadHandler.DownloadStatusChanged += isActive =>
{
DownloadHintVisibility = isActive ? Visibility.Visible : Visibility.Collapsed;
};
//browser.ResourceRequestHandlerFactory = new CustomResourceRequestHandlerFactory();
//browser.SetZoom(3);
});
private CefSharp.RequestContext CreateNewRequestContext(string subDirName, string host)
{
var contextSettings = new RequestContextSettings
{
PersistSessionCookies = false,
CachePath = Path.Combine(Cef.GetGlobalRequestContext().CachePath, subDirName),
};
var context = new CefSharp.RequestContext(contextSettings);
Cef.UIThreadTaskFactory.StartNew(() =>
{
var settings = new Dictionary<string, string>
{
["auth.server_whitelist"] = $"*{host}*",
["auth.negotiate_delegate_whitelist"] = $"*{host}*",
};
foreach (var s in settings)
if (!context.SetPreference(s.Key, s.Value, out var error))
Log.Warning("Error setting '{SKey}': {Error}", s.Key, error);
});
return context;
}
private ICommand _startPageCommand;
public ICommand StartPageCommand => _startPageCommand ??= new RelayCommand(obj =>
{
StartPageVisibility = Visibility.Visible;
});
private ICommand _backCommand;
public ICommand BackCommand => _backCommand ??= new RelayCommand(obj =>
{
if (StartPageVisibility == Visibility.Visible)
{
StartPageVisibility = Visibility.Collapsed;
return;
}
if (Browser.CanGoBack)
Browser?.GoBack();
});
private ICommand _urlCommand;
public ICommand UrlCommand => _urlCommand ??= new RelayCommand(obj =>
{
if (!(obj is string url))
return;
Address = url;
StartPageVisibility = Visibility.Collapsed;
});
private ICommand _exitCommand;
public ICommand ExitCommand => _exitCommand ??= new RelayCommand(obj => { Application.Current.Shutdown(); });
}
}
Is there a way to force CefSharp to show the authentication prompt or handle Windows Auth properly? Perhaps by injecting credentials programmatically or using a different handler? Any insights or alternative approaches would be appreciated.