
Intro
In the first part of this series I've shown some basic concepts of creating a credential provider in Windows, things like what credential provider is, what components make it up, and which COM interfaces it relies on. All of that stuff will be required to follow on in this blog post. So please be sure to read that first.
To recap it very briefly, a credential provider's purpose is to collect credentials needed to log in users to their Windows accounts. And, under the hood, a credential provider is just a collection of COM objects that are invoked by a host caller (in many cases a LogonUI process) that perform a certain task.
Thus, what helped me to grasp how to code my own credential provider was to understand its life cycle. Or, in other words, what callbacks come when and what they do. And this is what I will dedicate this part of the series for.
So without further adieu, let's start from the very beginning of the credential provider's life.
Table Of Contents
For an easier navigation here's the table of contents:
Nomenclature
To simplify the contents of this post let me come up with the following naming convention:
- MyFactorywill be the name for my factory class.
- MyFilterwill be my filter class.
- MyProviderwill be my provider class.
- MyCredentialwill be my credential class.
I already described what each of those classes does in part 1 of this series. So click the links above to read about each class.
Additionally, I showed what "tiles" and "UI fields" are in a credential provider.
Registration
As most of the COM things, a credential provider needs to register itself in the system registry. It has a few other places to do so than just your run-of-the-mill COM component.
The credential provider needs to register its filter and provider classes in the following keys, respectively:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential ProvidersFor that create two unique CLSIDs. You can do it using Microsoft's "Create GUID" tool in Visual Studio.
For the purpose of this demo I will be using the following CLSIDs. But don't borrow mine. If you do that you will create a naming conflict:
{855E2A67-A476-4664-8581-01BEC33975BF}forMyFilterclass.
{855E2A67-A476-4664-8581-01BEC33975BC}forMyProviderclass.I separated them just by the last hex digit for readability.
The format to register MyFilter class is as follows:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters\{855E2A67-A476-4664-8581-01BEC33975BF}]
@="MyCredentialProvider Filter"
[HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BF}]
@="MyCredentialProvider Filter"
[HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BF}\InprocServer32]
@="path\\to\\MyCredentialProvider.dll"
"ThreadingModel"="Apartment"The default string value for each key does not matter, except for the InprocServer32 key. It must contain the path to your credential provider DLL.
	Even though it is possible to specify only a DLL file name in the InprocServer32 key, for security purposes, I would strongly advise to use the absolute file path to prevent any ambiguity.
IMPORTANT: Make sure to place your credential provider DLL somewhere in a write-protected folder, where only administrators have the write access to it! Otherwise you will be creating a security loophole.A good location would be the
%ProgramW6432%system directory under your company name. For instance:"C:\Program Files\My Company\Credential Provider\MyCredentialProvider.dll"
Because you may be testing your credential provider in a virtual machine (VM), with the debugger build configuration, make sure that its DLLs can be loaded in that OS. You can use my WinAPI Search tool to determine if all of its dependent modules are present in that OS. Otherwise your credential provider won't load and you won't see much when you attempt to test it.
Then the format to register MyProvider class is as follows:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{855E2A67-A476-4664-8581-01BEC33975BC}]
@="MyProvider Password MFA"
[HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BC}]
@="MyProvider Password MFA"
[HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BC}\InprocServer32]
@="path\\to\\MyCredentialProvider.dll"
"ThreadingModel"="Apartment"Although you can technically separate your filter and provider classes into different modules (DLLs), it is highly impractical, and most credential providers don't do that.
The names for the keys should describe what your provider does. In my case I'm writing a generic password provider with a secondary authentication, or MFA.
After you set those registry keys, the credential provider will be good to go. No reboot is necessary. All you need to test it, among other things, is to lock the workstation, or to log out the user. If everything goes well, you should see your credential provider in action.
Keep in mind that a registered credential provider could be immediately used not only during a login process, but also from the Windows Explorer for some UAC prompts, and from other apps that may require Windows user account verification.
Having said that, I need to warn you that it is very easy to mess up your operating system with a faulty credential provider!
Debugging & Testing
It should probably go without saying that you should not install and test your credential provider on the same system that you are developing it on, or on any production or important PC that you don't want to lose access to.
By its nature a credential provider loads before you can log in to your system, and thus if you mess something up, you are risking to bork that computer!Because of that, always test your credential provider in a virtual machine!
Although for the most hardcore developers, it is possible to debug a credential provider on the same system, installing it in a VM greatly simplifies things. So don't be a masochist.
Debugging a credential provider is an entire subject of its own, thus I will leave it for a future blog post.
Let me briefly mention that the best way to see what is happening in a credential provider is to use diagnostic logging (into a text file). And as debugging itself is concerned, you can use Visual Studio Remote Debugger to attach to the host process, or LogonUI.exe for the login screen.
For now though let me review how you can uninstall your credential provider, an easy way, and a hard way, if you happened to have skipped this chapter before you installed it.
Uninstalling
Let's first review an easy uninstallation, if you have access to any of the user accounts on the PC where the credential provider is installed.
De-registration
Simply remove the registry keys for the filter class:
I'm using my own CLSIDs for the filter and provider classes. You obviously need to substitute those GUIDs with your own.
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters\{855E2A67-A476-4664-8581-01BEC33975BF}]
[-HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BF}]And for the provider class:
[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{855E2A67-A476-4664-8581-01BEC33975BC}]
[-HKEY_CLASSES_ROOT\CLSID\{855E2A67-A476-4664-8581-01BEC33975BC}]And that is pretty much it. Your credential provider will not be loaded anymore. No reboot is necessary.
De-registration "The Hard Way"
But, if you don't have access to a user account on the PC where you installed your credential provider, you will need to uninstall it the hard way.
The easy-hard-way is to create a snapshot in a VM before you install it. And then simply revert the snapshot if something goes wrong. You will lose all your diagnostic logs and other data that way, but at least it is fast.
But if you didn't make a snapshot to revert to, or if you're testing it on a physical PC, follow these steps to remove your credential provider:
- Press and hold the power button to initiate a hard reset. In a VM, there should be a menu option for that.
- While the OS is rebooting, initiate a hard reset once again. This should put it into a recovery mode the next time it boots.
		If the recovery mode doesn't come up, you may want to repeat the hard reset for a few times. Or, alternatively, you can press and hold the F9 (or, on some systems the F12) keyboard key during the initial boot sequence to initiate it manually. 
- When you enter the recovery mode in Windows, select "Troubleshoot", then "Advanced Options" and "Command Prompt".
		
		You may need to provide your administrator password at that stage. 
- When the command prompt opens up, use the following commands to switch to the directory where you installed the credential provider and then remove its DLL:
	In the example above, I'm assuming that you installed your credential provider on drive C:, where thepath\tois the directory where you placed it in, andMyCredentialProvider.dllis the file name for the credential provider DLL.
- Reboot the system.
- You should not see your credential provider anymore and should be able to log in.
Be careful next time and please heed my advice that I gave above!
DLL Exports
Your credential provider DLL must have the following exported functions to be able to load as a COM component:
extern "C" __declspec(dllexport) HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv);
extern "C" __declspec(dllexport) HRESULT STDAPICALLTYPE DllCanUnloadNow();The DllCanUnloadNow function must implement module-wide reference counting, and return S_OK when the count reaches 0, and S_FALSE otherwise. This is your DLL's way of saying that no internal class is active in it.
And the DllGetClassObject function is where your credential provider begins its life cycle.
Initialization
Once registered your credential provider will go through the following stages when a host process tries to load it:
- From within the DllGetClassObjectcall you need to check therclsidparameter, that was passed into it, to be one of your CLSIDs that you registeredMyFilterclass andMyProviderclass with. And if so, create an instance ofMyFactoryclass for each of those CLSIDs, and invoke itsQueryInterfacemethod with theriidandppvparameters that were passed in theDllGetClassObjectcall.
- The IClassFactorywill then invoke theCreateInstancemethod of yourMyFactoryclass with theriidset toICredentialProvider. In turn yourMyFactoryclass should create an instance of yourMyProviderclass, and later call itsIUknown'sQueryInterfacewith theriidandppvthat were passed into theCreateInstancecall.
- Your MyProviderclass will then begin receiving its own calls to itsQueryInterfacemethod. The most obvious are requests for theICredentialProviderinterfaceriid. That is where it needs to return the pointer to its inheritedICredentialProviderinterface.To simplify dealing with pointers to inherited interfaces use the QITABENTandQITABENTMULTImacros in theQITABstruct and theQISearchfunction. (Check the description for theQISearchfunction for details.)Additionally, the QueryInterfacemethod forMyProviderclass may receive requests for theIAutoLogonProviderinterface at this early stage. It is an undocumented interface, that is declared as such:C++[Copy]MIDL_INTERFACE("8A4E89FE-C09D-475E-88CB-F8F11E047C50") IAutoLogonProvider : public IUnknown { virtual HRESULT STDMETHODCALLTYPE SetAutoLogonManager(IAutoLogonManager *) = 0; };The only method that is available in it deals with IAutoLogonManager, that seems to be declared as such (it is also undocumented):C++[Copy]struct IAutoLogonManager : public IUnknown { virtual HRESULT STDMETHODCALLTYPE IgnoreAutoLogonMode() = 0; virtual BOOL STDMETHODCALLTYPE IsAutoLogonMode() = 0; virtual BOOL STDMETHODCALLTYPE IsSingleUserNoPasswordAutoLogonMode() = 0; virtual BOOL STDMETHODCALLTYPE IsAutoLogonNotSingleUserNoPasswordMode() = 0; virtual BOOL STDMETHODCALLTYPE IsSystemAutoLogon() = 0; virtual HRESULT STDMETHODCALLTYPE UpdateAutoLogonDefaultSID() = 0; virtual HRESULT STDMETHODCALLTYPE ResetAutoLogonSetting() = 0; virtual HRESULT STDMETHODCALLTYPE ClearSystemAutoLogonSetting() = 0; virtual HRESULT STDMETHODCALLTYPE GetAutoLogonCredential(_Out_ PWSTR*, _Out_ PWSTR*, _Out_ PWSTR*) = 0; virtual BOOL STDMETHODCALLTYPE IsSingleUser() = 0; virtual HRESULT STDMETHODCALLTYPE GetSerializedAutoLogonCredential(ICredProviderCredentialSerialization * *) = 0; };Without having done too much reverse engineering of the IAutoLogonProviderinterface, I would surmise to say that it deals with automatic logins that credential providers support.
- After that your MyFactoryclass will receive another call to itsCreateInstancemethod withriidfor theICredentialProviderFilterinterface. As before, it needs to create an instance of yourMyFilterclass and invoke theQueryInterfaceon theIUnknowthat it inherited from, with theriidandppvthat it received in theCreateInstancecall.
- 
		This callback will take place if the system receives a remote desktop connection (RDP) request. Otherwise you won't see this callback. The purpose of this callback is to select the correct provider to redirect the serialized login data to. In case of the RDP connection such data comes from a remote computer. Note that this request arrives to the credential provider after the RDP client on the remote machine supplies the correct user credentials. Or, in other words, the received serialized data should contain valid credentials for one of the local users in the system where the credential provider is running. I will give an overview of an RDP connection later. The job of this callback is to substitute the provider (defined by its CLSID in the clsidCredentialProviderparameter) that it received in thepcpcsInparameter on the input, with the provider CLSID that the received serialized data must be redirected to. This is done in thepcpcsOutparameter.Later on, the provider class that was specified in the pcpcsOutparameter, will receive the same serialized data in itsSetSerializationcallback for the actual processing.Make sure to treat provided serialized binary login data in the pcpcsInparameter as if it came from an untrusted source!This method should return S_OKif it could substitute the provider, to proceed with the login data received. Otherwise it should return an error code.If this callback returns a failure error code, the remote user will be presented with a remote credential provider on their screen. After that, they will have to re-login using the remote credential provider. To determine if computer is currently receiving an RDP connection use the SM_REMOTESESSIONflag, with some additional checks, that are explained here.
- 
		Your MyFilterclass will receive a call to itsFiltermethod with the details of the usage scenario for your credential provider, and with the list of all available providers in the system.This is usually where you need to decide if you want to support such scenario in your credential provider. And if so, your job is to deny any providers that you do not want to support by setting the BOOLvalues in thergbAllowarray to FALSE. Otherwise, just leave them unchanged.I already showed the list of providers that my credential provider's filter received on Windows 10. This list may be obviously different on some other OS. You usually make your decision based on where your provider was called from. That will be supplied to you in the cpusparameter.For instance, if my MFA provider does not support CPUS_CHANGE_PASSWORD,CPUS_CREDUIorCPUS_PLAPusage scenarios, I should returnE_NOTIMPLfor them from myFiltercallback. This will begin the process of unloading of my credential provider from the host process.Otherwise, when you're done with your filtering logic, return S_OKfrom theFiltermethod if you want to continue loading your credential provider for that usage scenario.Note that if you do not return S_OKfrom theFiltermethod, your credential provider will receive only calls to the following methods before it is unloaded:Provider::SetUsageScenario,Provider::UnAdvise. Also note that in this case it will not receive a previously matching call toProvider::Advise.
- 
	
		Your MyProviderclass will receive this callback with details of the usage scenario, similar to what yourFilter::Filterhad received earlier. And thus your response to this callback should be coordinated with theFilter::Filtercallback.This callback basically tells MyProviderclass how your credential provider is loaded: whether it's a login screen, an unlocking of the workstation, a change-of-password screen, a call from theCredUIPromptForWindowsCredentialsfunction, etc.Return S_OKfor success, orE_NOTIMPLif you don't want to support such usage scenario.
- 
		This callback lets your MyProviderclass process the serialized input that was received from outside. Such input is usually received from a remote computer via the remote desktop connection (RDP) request, or as a result of a call to theCredUIPromptForWindowsCredentialsfunction by some other process (such as the Windows Explorer, when displaying some forms of the UAC prompt, etc.)Make sure to treat provided serialized binary login data in the pcpcsparameter as if it came from an untrusted source!Your MyProviderclass may not receive this callback in other cases. Otherwise, it will follow theUpdateRemoteCredentialcallback in yourMyFilterclass.The job of this method is to process serialized data that it received in the pcpcsparameter, and to fill in the UI fields in the appropriate credential. It may also determine if the supplied login info is enough to warrant an automatic login. If so, it may later proceed with the submit sequence at the later callbacks.The implementation in the default password credential provider in Windows performs an in-place decoding of received input parameters using an internal KerbInteractiveUnlockLogonUnpackInPlacefunction. You can find it in the official Microsoft sample.After that, if it succeeds at decoding the input credentials, it later initiates an autologon in the GetCredentialCountcallback.But, if provided serialized data is not enough, or if there's an error during de-serialization, this callback may return E_UNEXPECTED. In that case, the (remote) user requesting the login will be asked to re-enter their credentials.The format of the serialized binary data received in the input parameter depends on the provider. For system providers you can use the CredUnPackAuthenticationBufferWAPI to decode it.Thus, you can apply the following logic to try to de-serialize the data received: C++[Copy]#include <tchar.h> #include <wincred.h> #pragma comment(lib, "Credui.lib") BOOL DeserializeRemoteProviderLoginData( __in const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs) { BOOL bRes = FALSE; int nOSError = 0; if(pcpcs && pcpcs->rgbSerialization) { DWORD dwchSzUser = 0; DWORD dwchSzPwd = 0; DWORD dwFlags = CRED_PACK_PROTECTED_CREDENTIALS; BOOL bB = CredUnPackAuthenticationBufferW(dwFlags, pcpcs->rgbSerialization, pcpcs->cbSerialization, NULL, &dwchSzUser, NULL, NULL, NULL, &dwchSzPwd); nOSError = GetLastError(); if(!bB && nOSError == ERROR_INVALID_PARAMETER) { //Try an old method w/o password encryption dwFlags = 0; bB = CredUnPackAuthenticationBufferW(dwFlags, pcpcs->rgbSerialization, pcpcs->cbSerialization, NULL, &dwchSzUser, NULL, NULL, NULL, &dwchSzPwd); nOSError = GetLastError(); } if(!bB && nOSError == ERROR_INSUFFICIENT_BUFFER) { WCHAR* pUsr = new (std::nothrow) WCHAR[dwchSzUser]; if(pUsr) { WCHAR* pPwd = new (std::nothrow) WCHAR[dwchSzPwd]; if(pPwd) { if(CredUnPackAuthenticationBufferW(dwFlags, pcpcs->rgbSerialization, pcpcs->cbSerialization, pUsr, &dwchSzUser, NULL, NULL, pPwd, &dwchSzPwd)) { bRes = TRUE; nOSError = 0; wprintf(L"User: %s\n", pUsr); //DO NOT output, log, or store the password!!! wprintf(L"Password: %s\n", pPwd && pPwd[0] ? L"provided" : L"empty"); } else nOSError = GetLastError(); //Securely erase the password SecureZeroMemory(pPwd, dwchSzPwd); delete[] pPwd; pPwd = NULL; } else nOSError = ERROR_OUTOFMEMORY; delete[] pUsr; pUsr = NULL; } else nOSError = ERROR_OUTOFMEMORY; } } else nOSError = ERROR_EMPTY; SetLastError(nOSError); return bRes; }
- 
		This callback is invoked with the list of currently available users for the login. A usual implementation is to cache provided user data for future reference. This callback will not be available if you're using an older (version 1) provider class. Keep in mind that in some situations a host process may request to display a so-called "Other User" tile. Unlike regular tiles, such a tile gives user an option to enter their user name and password: In this case, the user array provided in the usersparameter will not include any information about the "Other User" tile. You can deduce its presence by calling theGetAccountOptionsmethod and by examining itscredentialProviderAccountOptionsparameter as such:C++[Copy]HRESULT STDMETHODCALLTYPE MyProvider::SetUserArray( _In_ ICredentialProviderUserArray* users) { //Determine if we need to display the "Other user" tile enum class OTHER_USER_TILE { NotNeeded, Needed, NeededMicrosoftAccount, }; OTHER_USER_TILE other_user = OTHER_USER_TILE::NotNeeded; CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS cpao; if(SUCCEEDED(users->GetAccountOptions(&cpao))) { if(cpao & CPAO_EMPTY_LOCAL) { if(cpao & CPAO_EMPTY_CONNECTED) { //"Microsoft account" is when you sign in using your email address or phone number. other_user = OTHER_USER_TILE::NeededMicrosoftAccount; } else { //Regular "Other user" tile other_user = OTHER_USER_TILE::Needed; } } } //Do other work ... }You can obtain the following information for the available users: user name (eg: Admin), user's display name (or the name, that could be added after a user account was created, eg:Admin Account), "qualified user name" (eg:DESKTOP-14CH5ES\Admin), "logon status string" (eg:Locked), user's primary SID (eg:S-1-5-21-2104516720-2747548040-1419514401-1001) and "provider ID" (or special GUID for online Microsoft user accounts) using theGetStringValuefunction.
- 
		This optional callback in your MyFilter class may be invoked if the system needs to support pre-logon authentication for a credential provider, or PLAP. An example of such scenario would be the need for the 802.1x authentication to establish a network connection. In this case the cpusparameter will be set toCPUS_PLAP.The processing of this filter method should be similar to what I already described, with the exception that if you return E_NOTIMPLfrom this method, your credential provider will not be unloaded. It will tell the host process that it does not support the PLAP features.
- 
		This callback is invoked to ask your MyProvider class if it wants to use certain events (through the ICredentialProviderEventsinterface). For instance, if you need to refresh (or redraw) the tiles in your credential provider, you can do so by calling theCredentialsChangedfunction. But for that you need to retain a copy of theICredentialProviderEventsinterface in yourAdvisecallback.To retain a copy of the interface simply increment the reference count by using its AddRefmethod. In that case though, remember to dereference it in the correspondingUnAdvisecallback by calling itsReleasemethod.If you do not need this callback, simply return E_NOTIMPLfrom it.
Filter::UpdateRemoteCredential
Filter::Filter
Provider::SetUsageScenario
Provider::SetSerialization
Provider::SetUserArray
Filter::Filter (PLAP)
Provider::Advise
User Interface Initialization
The following sequence of callbacks will be sent to your credential provider to initialize the user interface (UI) components:
From a perspective of a UI developer, the way credential provider's UI is structured may appear odd. It works by the host process asking for UI elements and by yourMyProvider(orMyCredential) classes responding. There's not always a freedom for the latter classes to simply draw their UI elements at some arbitrary location or of an arbitrary size, nor to resize, move or to recreate themselves.
- 
		This method is called to let your provider specify how many tiles it wants to show and whether or not it wants to use the autologon feature. Note that the number of tiles may not always match the number of users, since some providers may not support all user accounts. Set the number of tiles that you want to display in the pdwCountparameter. This number must also include the "Other User" tile, if the host process requested it in theSetUserArraycallback.Note that once you specify the number of tiles in this callback you will not be able to add or remove them. In case your provider wants to initiate the autologon, it needs to set the pdwDefaultparameter to the index of the tile that it wants to use for it, and thepbAutoLogonWithDefaultparameter to point to a TRUE value. Additionally, it needs to fill out the UI fields for the default tile in yourMyCredentialclass.The autologon is a feature of the credential provider, that instructs it to try to perform user login automatically, or without waiting for a user to type in their login credentials. The autologon may be used during an RDP login, or for an automatic logon that could be set up via the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogonsystem registry key.Note that the autologon is not guaranteed to succeed, and it may be aborted if the automatically provided user credentials are not validated. In that case the credential provider should behave as if the autologon was not initiated. If autologon is not needed, set the credential index pointed by pdwDefaulttoCREDENTIAL_PROVIDER_NO_DEFAULT, or -1.
- 
		This method is called to retrieve a pointer to an instance of your MyCredentialclass for each tile by its index, supplied in thedwIndexparameter. Tile indexes go sequentially from 0 to the number of tiles (minus one) specified in theGetCredentialCountcallback.Remember that a "tile" is a visual representation of a "credential" class. The usual implementation of this callback is to create an instance of your MyCredentialclass for each invocation of this callback and to pass it back to the host process in theppcpcparameter.
- 
		Is called to retrieve the number of UI fields that will be ever needed for the usage scenario for all tiles in your MyProviderclass. Set thepdwCountparameter to the overall number of UI fields.Remember that UI fields are controls on the screen that a user interacts with to provide the login credentials. These could be text input fields, buttons, checkboxes, etc. Once UI fields are created you will not be able to add or remove them. There are some limited forms of modifications that UI fields allow, like changing their text, or hiding/showing them. The usual implementation of this callback is to create a large array of UI fields and set them as hidden in the following GetFieldStatecallbacks. After that the logic in your credential provider can show or hide UI fields in response to other callbacks, such as when tiles become selected or deselected.
- 
		This callback is invoked for each UI field to request its order on the screen, unique ID, text and its type, i.e. whether it's a text, input/password box, checkbox, etc. The number of UI fields is defined by a previous call to the GetFieldDescriptorCountmethod.Every invocation of this callback has a zero-based index of the UI field, provided in the dwIndexparameter. The job of this method is to fill out theCREDENTIAL_PROVIDER_FIELD_DESCRIPTORstruct and to return it in theppcpfdparameter.You will need to allocate the memory for the CREDENTIAL_PROVIDER_FIELD_DESCRIPTORstruct using theCoTaskMemAllocfunction, as well as to allocate strings in it using theSHStrDupWAPI, similar to the following code snippet:C++[Copy]HRESULT STDMETHODCALLTYPE MyProvider::GetFieldDescriptorAt(__in DWORD dwIndex, __deref_out CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR** ppcpfd) { HRESULT hr; CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *pD = (CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR*) CoTaskMemAlloc(sizeof(CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR)); if(pD) { hr = SHStrDupW(source_cred_fields[dwIndex].label, &pD->pszLabel); if(SUCCEEDED(hr)) { pD->dwFieldID = source_cred_fields[dwIndex].dwID; pD->cpft = source_cred_fields[dwIndex].type; pD->guidFieldType = source_cred_fields[dwIndex].guid; //Free old memory (if it's there, in case of a "wrapper") if(*ppcpfd) { CoTaskMemFree(*ppcpfd); } *ppcpfd = pD; } else { //Failed to alloc CoTaskMemFree(pD); } } else hr = E_OUTOFMEMORY; //Do other work ... }Where: C++[Copy]const struct { DWORD dwID; CREDENTIAL_PROVIDER_FIELD_TYPE type; LPCWSTR label; GUID guid; } source_cred_fields[] = { { 0, CPFT_LARGE_TEXT, L"Large text", {}, }, { 1, CPFT_EDIT_TEXT, L"User name", CPFG_LOGON_USERNAME, }, { 2, CPFT_PASSWORD_TEXT, L"Password field", CPFG_LOGON_PASSWORD, }, //... };Most UI field types are self-explanatory, but some require clarification: - First off, the guidFieldTypemember of theCREDENTIAL_PROVIDER_FIELD_DESCRIPTORstruct is an extension of the UI field type. It allows you to specify an extra meaning for that field. You can use your own unique GUID for that, or use Microsoft-defined ones, likeCPFG_LOGON_USERNAMEorCPFG_LOGON_PASSWORDin the code example above. In case of the latter ones, they add special properties to the field, that could be recognized by the host process.
- CPFT_COMMAND_LINKis a clickable text (or a link) that you can respond to in your- CommandLinkClickedcallback. It can also be transformed into a button.
- CPFT_TILE_IMAGEfield type with the- CPFG_CREDENTIAL_PROVIDER_LOGOextension is a special field that displays your provider's logo. You can see UI images of this type when you click the "Sign in options" link:
- CPFT_SMALL_TEXTfield type with the- CPFG_CREDENTIAL_PROVIDER_LABELextension is another special field that contains an accessibility user prompt for the provider logo that I showed above. You can see such prompt if you hover the mouse pointer over one of the UI images for a provider in the "Sign in options" section.
 To let you better visualize what UI fields are available for a credential, let's assume that you specified the following fields in your GetFieldDescriptorAtcallback in this particular order:ID Type Text guidFieldType 1 CPFT_SMALL_TEXT"Small text" 2 CPFT_LARGE_TEXT"Large Text" 3 CPFT_EDIT_TEXT"Edit field - User name" CPFG_LOGON_USERNAME4 CPFT_EDIT_TEXT"Edit field" 5 CPFT_PASSWORD_TEXT"Password Field 1" CPFG_LOGON_PASSWORD6 CPFT_PASSWORD_TEXT"Password Field 2" 7 CPFT_COMBOBOX"Combo field" 8 CPFT_CHECKBOX"Checkbox 1" 9 CPFT_COMMAND_LINK"Command link" 10 CPFT_TILE_IMAGE"Tile image 1" 11 CPFT_TILE_IMAGE"Provider logo" CPFG_CREDENTIAL_PROVIDER_LOGO12 CPFT_SMALL_TEXT"Provider label" CPFG_CREDENTIAL_PROVIDER_LABEL13 CPFT_SUBMIT_BUTTON"Submit Btn" These UI fields can be rendered as follows, marked with their IDs, in the login credential provider: There are multiple restrictions that apply to UI fields: - The order at which you specify UI fields matter. The first fields will appear on top.
- Note that hidden UI fields do not take any space on the screen.
- You cannot specify the position of a UI field on the screen. You can only specify its order.
- All UI fields will be spaced vertically by their order. With the exception of the "submit" button, UI fields cannot be placed horizontally next to each other.
- You cannot specify the size, or style of a UI field.
- On Windows 10 and later OS, the login screen (or LogonUI.exehost process) will always display a user account image on top, followed by the user account name in the selected tile. You cannot disable, alter, or style it from your credential provider.
- CPFT_LARGE_TEXTfield type seems to be ignored by- LogonUI.exehost process on Windows 10 and later OS. It is always rendered as the- CPFT_SMALL_TEXTtype.
- CPFG_LOGON_USERNAMEand- CPFG_LOGON_PASSWORDare special extension GUIDs for the- CPFT_EDIT_TEXTfield type that hint to the host process which fields contain login credentials for the "submit" button.
- CPFT_TILE_IMAGEfield type may contain any arbitrary bitmap and a label under it. Your- MyCredentialclass can specify it in the- GetBitmapValuecallback.- Note that the size of such image is defined by the host process and cannot be changed from the credential provider. 
- CPFT_TILE_IMAGEfield type was used to display a user account image if you used an older V1 credential provider. The credential provider derived from the V1 provider class could specify its own bitmap for it. The newer provider class does not support this functionality and the user account bitmap is rendered by the host process (i.e.- LogonUI.exe).
- CPFT_TILE_IMAGEfield type with the- CPFG_CREDENTIAL_PROVIDER_LOGOextension GUID allows to specify an arbitrary bitmap to be used for your- MyProviderclass. It is usually created with the- CPFS_DISPLAY_IN_DESELECTED_TILEattribute for a credential. With that setup, it will be displayed by default in the "Sign in options" section, as I showed above.
- CPFT_SMALL_TEXTfield type with the- CPFG_CREDENTIAL_PROVIDER_LABELextension GUID allows to specify an accessibility label for the bitmap image for your- MyProviderclass. It should be also created with the- CPFS_DISPLAY_IN_DESELECTED_TILEattribute.
- CPFT_SUBMIT_BUTTONis a special field type for the "submit" button. It has a few unique properties. There can be only one submit button. Although there could be no submit buttons, like for the- NgcPinProvider. Additionally, Microsoft recommends attaching your submit button to another control, usually to a password field. You can do so by responding to the- GetSubmitButtonValuecallback in your- MyCredentialclass. After that, the "submit" button will appear on the side of that UI field, as I showed in the screenshot above (item with ID=13).- In case you do not want to attach your submit button to any fields, create it with the - CPFG_STANDALONE_SUBMIT_BUTTONextended guidFieldType:C++[Copy]- // {0b7b0ad8-cc36-4d59-802b-82f714fa7022} DEFINE_GUID(CPFG_STANDALONE_SUBMIT_BUTTON, 0x0b7b0ad8, 0xcc36, 0x4d59, 0x80, 0x2b, 0x82, 0xf7, 0x14, 0xfa, 0x70, 0x22);- And return its own field index in the - GetSubmitButtonValuecallback. In that case the credential provider will display it as such:
- To create a button, that is not a "submit" one, use the CPFT_COMMAND_LINKfield type with theCPFG_STYLE_LINK_AS_BUTTONextended GUID:C++[Copy]// {088fa508-94a6-4430-a4cb-6fc6e3c0b9e2} DEFINE_GUID(CPFG_STYLE_LINK_AS_BUTTON, 0x088fa508, 0x94a6, 0x4430, 0xa4, 0xcb, 0x6f, 0xc6, 0xe3, 0xc0, 0xb9, 0xe2);This will turn the command link from my screenshot above (with the ID=9) into a button: You can then interact with it in the same way as you would with the command link, namely, you would generally process CommandLinkClickedcallbacks when it is clicked.
- Note that the amount of vertical space available for your UI fields is quite limited, and if you go over it, the LogonUI.exewill hide your lower fields and display an ugly vertical scrollbar to reach them:There's no documented way to know the amount of available vertical space, and whether or not the scrollbar is shown. 
- A CPFT_COMBOBOXfield type displays a dropdown box where a user can select from the list of options. You will need to specify the number of items in that list in theGetComboBoxValueCountcallback, and then provide text for each of those options in theGetComboBoxValueAtcallback for yourMyCredentialclass:Note that unlike desktop UI combo boxes, the one available for credential providers does not allow edited input, or specifying a list containing anything other than text. 
 
- First off, the 
Provider::GetCredentialCount
Provider::GetCredentialAt
Provider::GetFieldDescriptorCount
Provider::GetFieldDescriptorAt
User Interface Callbacks
The following callbacks may be dispatched to your MyCredential class, based on the UI fields that you specified for it:
- 
		Is called to retrieve text for a UI field by its dwFieldID. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.Keep in mind that you need to return a requested string using the SHStrDupWfunction, as such:C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetStringValue( __in DWORD dwFieldID, __deref_out PWSTR* ppwsz) { HRESULT hr; if(dwFieldID == FIELD_ID_PWD_PROMPT) { const WCHAR* pstrTxt = L"Please enter password"; hr = SHStrDupW(pstrTxt, ppwsz); } else { hr = E_NOTIMPL; } //Do other work ... }
- 
		Is called to retrieve UI field state by its dwFieldID, such as focused, disabled, hidden, etc. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.The UI field state should be returned in the pcpfsparameter, while additional state flags, in thepcpfisparameter.This callback is where you would display or hide certain UI fields, based on some specific criteria. 
- 
		Is called to ask for options for the UI field by its fieldID. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.You will receive this callback if you derived your MyCredentialclass fromICredentialProviderCredentialWithFieldOptionsinterface.This callback lets you specify some additional options for the UI field, such as the button to briefly reveal the password, or to limit input to an email or to numbers only, or to specify which on-screen keyboard to show: These are handy UI enhancements for your credential provider. So don't ignore them. 
- 
		Allows to specify a bitmap image for the UI fields of the CPFT_TILE_IMAGEtype using raw pixels. The UI field is identified by its ID, that was specified in theGetFieldDescriptorAtcallback.This callback in not officially documented. Your MyCredentialclass must be derived fromICredentialProviderCredential3or later to receive this callback.The pixel data must be supplied in the BITMAPINFOstruct and allocated using theCoTaskMemAllocfunction.Here's an example how you can specify an icon for your provider in the "Sign in options" area: C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetBitmapBufferValue( __in DWORD fieldID, __out DWORD* pImageBufferSize, __out BYTE** ppImageBuffer) { HRESULT hr = E_NOTIMPL; if(fieldID == FIELD_ID_PROVIDER_IMAGE) { //Let's use the system error icon for our test HICON hIcon = LoadIcon(NULL, IDI_ERROR); if(hIcon) { ICONINFOEX ix = {}; ix.cbSize = sizeof(ix); if(GetIconInfoExW(hIcon, &ix)) { HBITMAP hBmp = ix.hbmColor; BITMAP bm = {}; if(GetObjectW(hBmp, sizeof(BITMAP), &bm)) { HDC hDC = GetDC(NULL); if(hDC) { WORD wBitCount = 32; DWORD dwBmpSize = ((bm.bmWidth * wBitCount + 31) / 32) * 4 * bm.bmHeight; DWORD dwcbFullSize = sizeof(BITMAPINFOHEADER) + dwBmpSize; BYTE* lpbitmap = (BYTE*)CoTaskMemAlloc(dwcbFullSize); if(lpbitmap) { memset(lpbitmap, 0, dwcbFullSize); BITMAPINFO* pBMI = (BITMAPINFO*)lpbitmap; pBMI->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pBMI->bmiHeader.biWidth = bm.bmWidth; pBMI->bmiHeader.biHeight = bm.bmHeight; pBMI->bmiHeader.biPlanes = 1; pBMI->bmiHeader.biBitCount = wBitCount; pBMI->bmiHeader.biCompression = BI_RGB; //Retrieve bitmap bits if(GetDIBits(hDC, hBmp, 0, (UINT)bm.bmHeight, pBMI->bmiColors, pBMI, DIB_RGB_COLORS)) { //Pass our memory array to the host process *pImageBufferSize = dwcbFullSize; *ppImageBuffer = lpbitmap; lpbitmap = NULL; hr = S_OK; } else { //Failed - free memory CoTaskMemFree(lpbitmap); lpbitmap = NULL; } } //Remember to free the DC ReleaseDC(NULL, hDC); } } //Free icon resources DeleteObject(ix.hbmColor); DeleteObject(ix.hbmMask); } } } //Do other work ... }This should result in the following image: Note that alternatively you can return a failure error code from the GetBitmapBufferValuecallback to use an older, but documentedGetBitmapValuecallback instead.
- 
		Is called to retrieve a bitmap for the UI field of the CPFT_TILE_IMAGEtype. The UI field is identified by its ID in thedwFieldIDparameter, that was specified in theGetFieldDescriptorAtcallback.Even though this callback is invoked in all cases for the UI fields of the CPFT_TILE_IMAGEtype, the provided bitmap will be used only if the previousGetBitmapBufferValuecallback returned a failure code, or was not implemented.The bitmap must be returned in the phbmpparameter. Here's an example:C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetBitmapValue( __in DWORD dwFieldID, __out HBITMAP* phbmp) { HRESULT hr = E_NOTIMPL; if(dwFieldID == FIELD_ID_PROVIDER_IMAGE) { //Let's use the system warning icon for our test HICON hIcon = LoadIcon(NULL, IDI_WARNING); if(hIcon) { ICONINFOEX ix = {}; ix.cbSize = sizeof(ix); if(GetIconInfoExW(hIcon, &ix)) { //Return the color bitmap *phbmp = ix.hbmColor; //Remember to free the mask bitmap DeleteObject(ix.hbmMask); hr = S_OK; } } } //Do other work ... }This will result in a warning icon being displayed as your provider image, similar to what I showed above. Unfortunately there's no documented way of knowing which background color to use for the image. For instance, for the provider icons in the "Sign in options" area, Microsoft themselves aren't using any specific system color. They use a prepared bitmap that already comes with a background color. In other words, they don't use transparency. You can find this bitmap as the RT_BITMAPresource in the system credential providers. For instance, for thePasswordCredentialProviderprovider, the bitmap is in thecredprovs.dllmodule in the System32 folder. On my Windows 10 that DLL has only one bitmap resource of the size 72x72 pixels with the ID of13500, as I showed here.Thus, if you want to know the background color, you can load the bitmap that Microsoft used with the LoadBitmapWfunction from the appropriate module. (Say, forcredprovs.dllthere's only one bitmap resource in it.) And then extract the pixel color from it at each corner. This can be a hack to give you the background color that Microsoft uses:C++[Copy]BOOL GetProviderBitmapBkgndColor(__out COLORREF* pclrBkgnd) { BOOL bRes = FALSE; COLORREF clrBkgnd = {}; HMODULE hMod = LoadLibraryExW(L"credprovs.dll", NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_SEARCH_SYSTEM32); if(hMod) { HBITMAP hBmp = LoadBitmap(hMod, MAKEINTRESOURCE(13500)); if(hBmp) { //Get bitmap size BITMAP bm = {}; if(GetObject(hBmp, sizeof(bm), &bm)) { HDC hDC = CreateCompatibleDC(NULL); if(hDC) { HGDIOBJ hOldBmp = SelectObject(hDC, hBmp); #define PX_OFFSET 3 if(bm.bmWidth > (PX_OFFSET * 2) && bm.bmHeight > (PX_OFFSET * 2)) { //Sanity check: Get color from all 4 corners at a certain offset, // in case there's a border COLORREF clrTL = GetPixel(hDC, PX_OFFSET, PX_OFFSET); COLORREF clrTR = GetPixel(hDC, bm.bmWidth - PX_OFFSET - 1, PX_OFFSET); COLORREF clrBL = GetPixel(hDC, PX_OFFSET, bm.bmHeight - PX_OFFSET - 1); COLORREF clrBR = GetPixel(hDC, bm.bmWidth - PX_OFFSET - 1, bm.bmHeight - PX_OFFSET - 1); //All 4 colors must be the same to use them if(clrTL == clrTR && clrTR == clrBL && clrBL == clrBR) { //Can use it clrBkgnd = clrTL; bRes = TRUE; } } SelectObject(hDC, hOldBmp); DeleteDC(hDC); } } DeleteObject(hBmp); } FreeLibrary(hMod); } if(pclrBkgnd) *pclrBkgnd = clrBkgnd; return bRes; }On my Windows 10 and 11, that background color was #474747.
- 
		Is called to retrieve whether the UI field of the CPFT_CHECKBOXtype is checked or not, as well as to get the label for the checkbox itself. The UI field is identified by its ID in thedwFieldIDparameter, that was specified in theGetFieldDescriptorAtcallback.Return the checkbox state in the pbCheckedparameter, and the label text inppszLabel.Keep in mind that the text label must be allocated with the SHStrDupWfunction, similar to what I showed here.
- 
		Is called to get the number of items to display in the list for the UI field of the CPFT_COMBOBOXtype. The UI field is identified by its ID in thedwFieldIDparameter, that was specified in theGetFieldDescriptorAtcallback.Specify the count of items in the pcItemsparameter, and the index of the selected item inpdwSelectedItem.
- 
		Is called to retrieve the text label for each item in the list for the UI field of the CPFT_COMBOBOXtype. This callback is invoked afterGetComboBoxValueCount. The UI field is identified by its ID in thedwFieldIDparameter, that was specified in theGetFieldDescriptorAtcallback.The list item is identified by its index in the dwItemparameter. The job of this callback is to return the text for the list item in theppszItemparameter.Make sure to allocate the text label with the SHStrDupWfunction, similar to what I showed here.
- 
		Is called to ask which UI field should have the "submit" button attached to. A "submit" button is the button that initiates the user login process. The dwFieldIDparameter will contain the ID of the "submit" button in question.Provide the ID of another UI field that you want to attach the "submit" button to. (Field IDs are assigned during the GetFieldDescriptorAtcallback.)If you do not provide a valid ID, the "submit" button will be attached to the first visible UI field, which will look somewhat ugly. Additionally, Microsoft advises not to attach the "submit" button to static text controls or images. You can create a standalone "submit" button (without attaching it to anything) by following this advice. 
Credential::GetStringValue
Credential::GetFieldState
Credential::GetFieldOptions
Credential::GetBitmapBufferValue
Credential::GetBitmapValue
Credential::GetCheckboxValue
Credential::GetComboBoxValueCount
Credential::GetComboBoxValueAt
Credential::GetSubmitButtonValue
Post Initialization
The following callbacks are called among the last ones in the initialization sequence for your credential provider:
- 
		This callback is sent to your MyCredentialclass to retrieve the user SID that is associated with the tile. You can cache the user SID from the earlierSetUserArraycallback to yourMyProviderclass.You will receive this callback only if you implement the ICredentialProviderCredential2interface.Provide the requested user SID in the sidparameter. Keep in mind that it has to be passed as a string, and allocated with theSHStrDupWfunction, similar to what I showed here.You can convert user SID from the binary form to a string using the ConvertSidToStringSidfunction.In case of the "Other User" tile, return its user SID as a NULL. Additionally, return S_FALSE from the GetUserSidcallback.
- 
		Is called to let your MyCredentialclass(-es) to get pointers to theICredentialProviderCredentialEvents, or later interface. It is provided to it in thepcpceparameter. Make sure toAddRefit and thenReleaseit in the matchingUnAdvisecallback.The ICredentialProviderCredentialEventsinterface can be used for multiple purposes, mostly to interact with the host process UI:- One of its most often used functions is to request an update for the UI fields in your MyCredentialclass.Because unlike traditional UI, you cannot have handles to the UI fields displayed by the host process to update them on-demand. The only way for you to update them is by responding to callbacks. Thus you need a way to request those callbacks from the host process. 
- The only viable way to display your own custom UI from a credential provider, is to request the HWNDhandle for the host process window. This can be done via a call to theOnCreatingWindowfunction. After that use returned window handle as a parent for your own window(s).One very important thing to remember, if you decide to show your own custom UI, is that the thread that the MyCredential::Advisecallback is invoked from, as well as any other callbacks that you may receive in yourMyCredentialandMyProviderclasses, may not be the UI thread that theHWNDwindow returned by theOnCreatingWindowcall is running in.Thus, if you attempt to create a window from a different thread than the parent window, this may result in some very unexpected behavior and UI artifacts that are hard to diagnose and debug. As a workaround, make sure to subclass the window procedure for the window returned by OnCreatingWindow. Then send yourself some custom message to that window (say, using theWM_NULLmessage with a specialwParamorlParam), intercept it in your subclassedWndProc, and create your UI from that thread. Also make sure to remove your subclass from theUnAdvisecallback for yourMyCredentialclass.Note that you cannot use the SetWindowSubclassfunction for subclassing because you'll be doing it for a window in another thread.Alternatively, you may use the WH_CALLWNDPROCwindow hook to achieve the same effect.When designing your custom UI keep in mind that the login screen's credential provider can time-out and be unloaded at any time. If you parent all your UI from the window, returned by OnCreatingWindow, and don't display any blocking UI from your own windows, the host process (LogonUI.exe) will be able to close your UI gracefully when it decides to unload your provider. Doing it otherwise may lead to unexpected crashes.
 You may request pointers to the following newer interfaces, by QueryInterface-ing them fromICredentialProviderCredentialEvents:- ICredentialProviderCredentialEvents2that can be used to set on-demand some additional UI options like I showed above.
- ICredentialProviderCredentialEvents3is an undocumented interface that adds the following method:C++[Copy]- MIDL_INTERFACE("2D8DEEB8-1322-4973-8DF9-B282F2468290") ICredentialProviderCredentialEvents3 : public ICredentialProviderCredentialEvents2 { virtual HRESULT STDMETHODCALLTYPE SetFieldBitmapBuffer(__in ICredentialProviderCredential * pcpc, __in DWORD fieldID, __in DWORD imageBufferSize, __in BYTE const* pImageBuffer); };- Without going into too much reverse engineering, you can probably deduce what - SetFieldBitmapBuffermethod does to your- MyCredentialclass, remembering the- GetBitmapBufferValuemethod.
- ICredentialProviderCredentialEvents4is another undocumented interface that adds the following methods:C++[Copy]- MIDL_INTERFACE("DF50EA86-B7A9-4485-8F04-930A49686E5B") ICredentialProviderCredentialEvents4 : public ICredentialProviderCredentialEvents3 { virtual HRESULT STDMETHODCALLTYPE RequestSerialization(); virtual HRESULT STDMETHODCALLTYPE RequestSelection(); };- The - RequestSerializationmethod is designed to request serialization, or "submit" button action on demand.
- ICredentialProviderCredentialEvents5is one more undocumented interface that adds the following methods:C++[Copy]- MIDL_INTERFACE("c4a56475-d6f5-43e3-80ae-1aa99833cc05") ICredentialProviderCredentialEvents5 : public ICredentialProviderCredentialEvents4 { virtual HRESULT STDMETHODCALLTYPE SetTextFieldMaxLength( _In_ ICredentialProviderCredential * pcpc, _In_ DWORD dwFieldID, _In_ DWORD MaxLength) = 0; virtual HRESULT STDMETHODCALLTYPE SetAccessibilityTextForField( _In_ ICredentialProviderCredential *pcpc, _In_ DWORD dwFieldID, _In_ PCWSTR pcszText) = 0; virtual HRESULT STDMETHODCALLTYPE SetRawAccessibilityViewForField( _In_ ICredentialProviderCredential *pcpc, _In_ DWORD dwFieldID, _In_ BOOL bRaw) = 0; virtual HRESULT STDMETHODCALLTYPE RequestWebDialogVisibilityChange( _In_ ICredentialProviderCredential *pcpc, _In_ BOOL bVisible) = 0; };
 
- One of its most often used functions is to request an update for the UI fields in your 
Credential::GetUserSid
Credential::Advise
User Interaction & State Changes
The following callbacks may be invoked as a result of user interactions and state changes in the host process:
- 
		This callback is invoked when a tile is selected, either by a user, or automatically when your credential provider is loading. Only a single tile can be selected at a time. This callback must set the pbAutoLogonparameter to TRUE if yourMyCredentialclass wants to initiate an autologon for this user. This setting should be coordinated with a previousGetCredentialCountcallback. The autologon will initiate an automatic "submit" button action.There's no need to show or hide UI fields in response to this callback. You can set that up in the GetFieldStatecallback using theCPFS_DISPLAY_IN_SELECTED_TILEstate flag.Note that the fact that a tile has become selected, sadly, does not mean that it is visible. It may be obscured by what is known as a "curtain", or a screen that obscures all tiles and UI fields until the user clicks on it. You can use an undocumented SetDisplayStatecallback to determine when the "curtain" has been lifted.Although not specifically documented, your MyProviderclass (that created theMyCredentialclass, that in turn is associated with the tile) also becomes an active provider when the tile is selected.
- 
		Is called when a tile is deselected by a user. This callback is often followed by the SetSelectedcallback for the tile that was selected.Microsoft advises that a deselected tile should securely clear all sensitive information from memory of your MyCredentialclass, such as passwords, PINs and encryption keys.Microsoft has shown a good example in their official GitHub repo how you can safely scrub from memory all sensitive user data (such as plain-text passwords, PINs, keys, etc.) that could've been collected in your MyCredentialclass while it was selected.
- 
		Is invoked to notify your MyProviderclass about some external event.This callback in undocumented. So use it at your own risk. To receive this event, your MyProviderclass must be derived from theICredentialProviderWithDisplayStateinterface:C++[Copy]MIDL_INTERFACE("A09BCC29-D779-4513-BB59-B4DB5D82D2B6") ICredentialProviderWithDisplayState : public IUnknown { virtual HRESULT STDMETHODCALLTYPE SetDisplayState(__in CREDENTIAL_PROVIDER_DISPLAY_STATE_FLAGS Flags) = 0; };Your provider does not have to be active to receive this notification. The Flagsparameter receives a bitwise combination of flags that denote various types of notifications. I showed their meanings in the comments to theCREDENTIAL_PROVIDER_DISPLAY_STATE_FLAGSstruct. Some of those flags are not well studied, but some can be explained:- CPDSF_CURTAIN(or- 0x4) - is set when the "curtain" has been pulled down, or when it hides user tiles and corresponding UI fields.
- CPDSF_PROVIDER_VISIBLE(or- 0x80) - is set when your- MyProviderclass is visible.- To detect if a selected tile is visible to the user, check for the - CPDSF_PROVIDER_VISIBLEflag to be on, and for the- CPDSF_CURTAINflag to be off.
- CPDSF_SHUTDOWN_STARTED(or- 0x1000) - is set when the system initiated a reboot or shutdown. As a response to this notification you may hide any custom UI that your credential provider might have shown.
 
- 
		This callback is invoked every time a UI field that contains text is changed by the user. If it's a field with the CPFT_EDIT_TEXTorCPFT_PASSWORD_TEXTtypes, and the user types into it, this callback will be invoked for every single character that the user typed.The dwFieldIDparameter will contain the UI field ID that was changed, and thepszparameter will point to the new text as a null-terminated string. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.Use some internal variables in your MyCredentialclass to remember what the user types into text and password fields. You will need that later during "submit" button sequence.Be careful not to log what the user types into the credential provider, as it may contain very sensitive information, such as plain-text passwords, PINs, etc. Unlike a regular edit box, there's no way to restrict through this callback what the user types into a text-based UI field. 
- 
		It is called every time the user toggles the checkmark in the checkbox UI field. Such field has the CPFT_CHECKBOXtype. ThedwFieldIDparameter will denote the UI field that was changed, and thebCheckedwill contain the new value of the checkmark. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.This callback is also invoked if you call ICredentialProviderCredentialEvents::SetFieldCheckboxfunction on the UI field.Use some internal variables in your MyCredentialclass to remember user selection. You will need it later during "submit" button sequence.
- 
		Is called every time the user changes selected item in the combo-box for the UI field with the CPFT_COMBOBOXtype. ThedwFieldIDparameter will have the ID of the UI field that was changed. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.The dwSelectedItemparameter will contain the index of the list item in the combo box that was selected.This callback is also invoked if you call ICredentialProviderCredentialEvents::SetFieldComboBoxSelectedItemfunction on the UI field.Use some internal variables in your MyCredentialclass to remember user selection. You will need it later during "submit" button sequence.
- 
		Is called when a UI field with the CPFT_COMMAND_LINKtype was clicked by the user. ThedwFieldIDparameter will denote the UI field that was clicked. The UI field ID comes from what you specified in theGetFieldDescriptorAtcallback.Use some internal variables in your MyCredentialclass to remember user selection. You will need it later during "submit" button sequence.
Credential::SetSelected
Credential::SetDeselected
Provider::SetDisplayState
Credential::SetStringValue
Credential::SetCheckboxValue
Credential::SetComboBoxSelectedValue
Credential::CommandLinkClicked
Submit Button Sequence
When the user clicks "submit" button, or if such action is initiated automatically, this will involve the following sequence of callbacks:
- 
		Is invoked immediately after "submit" button click, either manually by the user, or as a result of autologon. This method is invoked only if you implemented the IConnectableCredentialProviderCredentialinterface in yourMyCredentialclass.The original purpose of this callback is to allow implementation of a slower pre-logon authentication before performing an actual authentication of a Windows user. An example of such requirement could be the 802.1x network that requires authentication before a Windows client computer can connect to a domain controller to authenticate Windows users. So in a sense, such Pre-Logon-Access Provider (or PLAP), also known as the SSO provider, acts as the first authenticator in the chain of two authentications needed to login a user. And since the network authentication can be a slow process, the Connectmethod was designed with two features: 1) The "Cancel" button to abort it, and 2) the status message to inform the user of the progress.The benefit of this callback is that you can reuse its "cancel" button and status text features for your own purposes. For instance, if you want to code a two-step login process, where you require a regular Windows user authentication, followed by an authentication using that user's smartphone, you can use the Connectmethod to implement it, as such:- First we check the user credentials in the Connectcallback and ensure that such user can login. In this case we will break Microsoft mantra and perform user authentication in our credential provider, say, by using theLsaLogonUserfunction.
- If the user login fails, we remember such state and return that error from the GetSerializationcallback, which will abort the login.
- But if the user login was successful, we serialize it (as we would do in the GetSerializationcallback, and remember it for that callback at a later iteration.)After that we perform our secondary authentication with the user's smartphone. This can be done using something similar to the following pseudo-code: C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::Connect(_In_ IQueryContinueWithStatus* pqcws) { HRESULT hr; //Checked user credentials with LsaLogonUser and received a valid user token. //Thus we know that the user login info is valid. //... //After that, performed needed serialization of the user login info and remembered it in some class variable... //... //Now ask the user to perform secondary authentication DWORD dwmsTimeout = 15 * 1000; //15 sec timeout //Class variable that stores the result of the secondary authentication _hrConnect = E_FAIL; //Display message for the user pqcws->SetStatusMessage(L"Please authenticate with your smartphone..."); //Go into a waiting loop for(DWORD dwmsIniTicks = GetTickCount();;) { //Ask our web server when the smartphone authentication is received HRESULT hrServerResult; if(PollRemoteServerAuthForResult(&hrServerResult)) { //Received a result from our web server pqcws->SetStatusMessage(L"Smartphone authentication finished"); _hrConnect = SUCCEEDED(hrServerResult) ? hrServerResult : E_UNEXPECTED; hr = S_OK; break; } //Simulate a short delay Sleep(500); //See if user wants to cancel hr = pqcws->QueryContinue(); if(hr == E_ABORT) { //User aborted pqcws->SetStatusMessage(L"User aborted"); _hrConnect = hr; break; } else if(hr != S_OK) { //Some other error pqcws->SetStatusMessage(L"Error authenticating"); _hrConnect = FAILED(hr) ? hr : E_UNEXPECTED; break; } //Check for timeout if(GetTickCount() - dwmsIniTicks >= dwmsTimeout) { //Timed out pqcws->SetStatusMessage(L"Timed out"); hr = _hrConnect = RPC_E_TIMEOUT; break; } } //Do other work ... }After the user clicks "submit" the code snippet above will display the following message: 
- If our secondary authentication fails (for whatever reason) we remember the error code and check it again in the GetSerializationcallback and return failure there, which will abort the login.
- But if our secondary authentication succeeds, we also pass it to our GetSerializationcallback, which retrieves our previously saved serialized user login data, and returns it to the host process, which proceeds with the user login.This could be visualized with the following pseudo-code: C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetSerialization( __out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr, __out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs, __deref_out_opt PWSTR* ppwszOptionalStatusText, __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon) { HRESULT hr = E_UNEXPECTED; //Did we succeed during the "Connect" stage? if(SUCCEEDED(_hrConnect)) { //Copy our previously serialized user login credentials in the Connect() callback //into the 'pcpcs' parameter using the CoTaskMemAlloc() function. //... //If success so far *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED; *ppwszOptionalStatusText = NULL; *pcpsiOptionalStatusIcon = CPSI_NONE; hr = S_OK; } else { //Failed during "Connect" stage *pcpgsr = CPGSR_NO_CREDENTIAL_FINISHED; *pcpcs = {}; //Pick the error message to display to the user std::wstring strError; if(_hrConnect == E_ABORT) { strError = L"User aborted smartphone authentication"; } else if(_hrConnect == RPC_E_TIMEOUT) { strError = L"Timed out waiting for the user to authenticate with the smartphone"; } else { //Some other error WCHAR buff[128] = {}; StringCchPrintf(buff, _countof(buff), L"Error (0x%08X) authenticating with the user's smartphone", _hrConnect); strError = buff; } SHStrDupW(strError.c_str(), ppwszOptionalStatusText); *pcpsiOptionalStatusIcon = _hrConnect != E_ABORT ? CPSI_ERROR : CPSI_WARNING; //Pass success hr = S_OK; } return hr; }Note that in this case we will be technically calling LsaLogonUserfunction twice, but that is a small price to pay for an added feature of the MFA login.If the user clicks the "cancel" button to abort secondary authentication, the code above will display the following message: 
 Note that the HRESULTstatus code returned from theConnectcallback tells the host process whether or not to display the "Disconnect" button, that I will describe next.
- First we check the user credentials in the 
- 
		This callback complements the Connectmethod. It is called when the user clicks the "Disconnect" button in the credential provider's UI:Such button will appear after Connectmethod returns a successful result code, such as S_OK. It will not be displayed ifConnectreturns an error.You can use the above-mentioned feature of the "Disconnect" button not to display it. Note that the "Disconnect" button will disappear from the UI after user clicks it. The intended purpose of this callback is to disconnect any pre-logon network connection that was established in the Connectmethod earlier.This method is invoked only if you implemented the IConnectableCredentialProviderCredentialinterface in yourMyCredentialclass.In case you are reusing the Connectmethod for your own purposes, not related to pre-logon network authentication, you will still need to implement this method, but you can safely ignore it by simply returning S_OK.
- 
		In case Connectmethod is implemented in yourMyCredentialclass, this callback is invoked right after it. Otherwise,GetSerializationwill be the first callback to be invoked after the user clicks "submit" button, or if such action is initiated after autologon.The job of this method is to serialize user-provided login credentials from the UI fields in the selected tile, and to pass the result to the host process. Serialization, in this sense, is the process of converting user login credentials into a binary byte array. This way such data can be passed between processes. The way credential provider's UI is structured is somewhat different from a traditional UI programming design. In a traditional app, you would ask a UI control to give you its text or some other selection in it. In a credential provider though there's no such mechanism. To receive what the user had specified in the UI fields you will need to save it in the internal variables in your MyCredentialclass while the user interacts with the UI fields. You can do so by intercepting callbacks such asSetStringValue,SetCheckboxValue,SetComboBoxSelectedValue,CommandLinkClicked, etc.I already showed above how you can incorporate the Connectmethod for the two-step MFA authentication. For now I'll assume only single-step logins.There are several ways how you can process this callback. One, you can evaluate user-provided credentials for immediate errors. Say, if the user did not provide their name for the "Other User" tile, or if the PIN for your PIN-provider is empty, etc. In that case you can immediately return an error without doing any serialization: C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetSerialization( __out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr, __out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs, __deref_out_opt PWSTR* ppwszOptionalStatusText, __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon) { HRESULT hr = E_UNEXPECTED; //Quickly evaluate user-provided credentials //... if(bBadLoginCredentials) { //Fail without doing serialization *pcpgsr = CPGSR_NO_CREDENTIAL_FINISHED; *pcpcs = {}; *pcpsiOptionalStatusIcon = CPSI_ERROR; SHStrDupW(L"Please provide valid login credentials", ppwszOptionalStatusText); return S_OK; } //Do other work ... }But for any other case you will need to serialize the login data. Microsoft have shown already an example how to do it for a password-provider in their official code sample. So I won't be repeating it. Make sure to provide a copy of the serialized login data in the pcpcsparameter using theCoTaskMemAllocfunction.When the serialized user credentials are ready, and there was no errors in the process, you can return success as follows: C++[Copy]HRESULT STDMETHODCALLTYPE MyCredential::GetSerialization( __out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr, __out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs, __deref_out_opt PWSTR* ppwszOptionalStatusText, __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon) { //After serialized data was copied into 'pcpcs' with CoTaskMemAlloc //... //Let the host process commence with the user login *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED; *ppwszOptionalStatusText = NULL; *pcpsiOptionalStatusIcon = CPSI_NONE; return S_OK; }Note that returning success (or S_OK) alone from the GetSerializationcallback will not grant an automatic login. For that you need to provide valid user login data in the serialized byte array. The decision to proceed with the user login will be made by the host process, based on the user login data that you give it in theGetSerializationcallback.
- 
		This callback is invoked after GetSerializationwith the results of the user login. ThentsStatusandntsSubstatusparameters will contain the status codes after calling theLsaLogonUserfunction on the serialized login data that was collected in theGetSerializationcallback.For details why you receive two status error codes for the login, refer to the LsaLogonUserfunction documentation.To check if the user login succeeded, it's enough to check the ntsStatusparameter to be STATUS_SUCCESS, or 0.If the login fails, make sure to specify the failure reason to be displayed to the user in the ppszOptionalStatusTextparameter. You can copy it there using theSHStrDupWfunction, similarly to what I showed here.In case of a successful login, there's no need to do anything else. Your credential provider will unload shortly thereafter. Microsoft showed how to process this callback in their GitHub sample. Keep in mind that if the ntsStatusparameter is set toSTATUS_SUCCESS, once you return from this callback, your credential provider will begin to unload and the user login will succeed. TheReportResultcallback serves as a notification only. It does not allow for your credential provider to stop the login.To prevent user login use the GetSerializationcallback instead.Do not show any custom UI from this callback as the desktop may be changing from a secure desktop to the one for the logged in user. Showing any UI at this late stage may prevent the user from properly interacting with it due to such transition. 
Credential::Connect
Credential::Disconnect
Credential::GetSerialization
Credential::ReportResult
Uninitialization
The following callbacks are invoked when a credential or provider goes out of scope, or when their respective classes are unloaded:
Note that your credential provider may be unloaded at any time, and not only after a successful user login. One such reason could be an elapsed timeout.
- 
		This callback is invoked before your MyCredentialclass is torn down. This is a good place to release all references to other interfaces that yourMyCredentialclass was holding and to free other resources.Alternatively return E_NOTIMPL if you do not care about this callback. Make sure that your code does not display any UI or blocks execution during this callback. 
- 
		This callback is invoked for your MyProviderclass before it is torn down, but after all instances of yourMyCredentialclass for it are deleted.Keep in mind that this callback may be called out-of-order, or without invoking a previous Advisemethod for yourMyProviderclass in case you block loading of the said provider class in theFiltercallback.Alternatively return E_NOTIMPL if you do not care about this callback. Make sure that your code does not display any UI or blocks execution during this callback. 
Credential::UnAdvise
Provider::UnAdvise
At this stage your credential provider will be invoking destructors for global classes and will be unloaded at any moment. Do not do any heavy processing at this stage.
Wrapping An Existing Credential Provider
The technique of wrapping a credential provider, usually the system one, is available because of the structure and operation of said credential providers that I show-cased above.
Wrapping is a handy technique to enhance an existing credential provider, similar to what subclassing does for Win32 windows.
Note that not all system credential providers can be wrapped easily. To be able to be wrapped a credential provider must play by the documented rules that I described in this post.The following system credential providers can be wrapped:
PasswordCredentialProvider,NgcPinProvider,SmartcardCredentialProvider,WLIDCredentialProvider,OnexCredentialProvider(with additional adjustments.)An example of the credential provider that does not play by the rules is
PicturePasswordLogonProvider.
Wrapping involves retrieving a pointer to the interface being wrapped and by invoking its callbacks from the same methods in your own interface. With that technique you will be able to alter the wrapped interface's input or output parameters to your liking, and thus modify the behavior of the credential provider that you are wrapping.
Review the following pseudo-code that alters the "submit" button behavior of the wrapped credential provider:
HRESULT STDMETHODCALLTYPE MyCredential::GetSerialization(
	__out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr,
    __out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs,
    __deref_out_opt PWSTR* ppwszOptionalStatusText,
    __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon)
{
	//Let the wrapped provider do all the work
	assert(_pWrappedCredential);
	hr = _pWrappedCredential->GetSerialization(pcpgsr, pcpcs, ppwszOptionalStatusText, pcpsiOptionalStatusIcon);
	if(SUCCEEDED(hr))
	{
		//Quick-and-dirty way to prevent users with short passwords from logging in
		WCHAR buffUsr[256];
		WCHAR buffPwd[256];
		DWORD dwchUser = _countof(buffUsr);
		DWORD dwchPwd = _countof(buffPwd);
		if(CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS, 
			pcpcs->rgbSerialization,
			pcpcs->cbSerialization,
			buffUsr, &dwchUser,
			NULL, NULL,
			buffPwd, &dwchPwd))
		{
			//Securely clear the password from memory
			SecureZeroMemory(buffPwd, sizeof(buffPwd));
			//Limit is 3 chars
			if(dwchPwd - 1 <= 3)
			{
				//Block this user
				*pcpgsr = CPGSR_NO_CREDENTIAL_FINISHED;
				*pcpcs = {};
				*pcpsiOptionalStatusIcon = CPSI_ERROR;
				SHStrDupW(L"Your password is too short",
					ppwszOptionalStatusText);
				//Reset user password field
				_pcpce->SetFieldString(static_cast<ICredentialProviderCredential*>(this), 
					PASSWORD_FIELD_ID, 
					L"");
				return S_OK;
			}
		}
	}
	return hr;
}In this case the _pWrappedCredential variable holds the pointer to the original wrapped credential class.
Microsoft uses this technique as well. Internally theOnexCredentialProviderclass is implemented as a wrapper for thePasswordCredentialProviderclass.
Keep in mind that credential provider wrapping carries an inherent risk of breaking things if the underlying wrapped interfaces are changed without notice in future versions of the OS.
Credential provider wrapping is a whole topic of its own. Thus, with enough interest I may write a separate blog post on that subject.
Specifics Of A Remote Desktop Connection
A remote desktop connection (or RDP) is somewhat different from a regular user login. It deserves a blog post of it's own, but until I write it, let's review it briefly here.
When a remote desktop connection is initiated you are technically dealing with two systems: the client computer that is making an RDP connection, and the remote computer that the user is connecting to. I'll use this nomenclature further down the description.
Let's review the sequence of events that take place during an RDP connection:
- A user initiates an RDP connection on the client computer by providing the remote computer's IP:
- If the remote computer is available, the RDP software running on the client computer will request the user credentials for the remote computer:
	
		
		Note that only if the user credentials match the ones on the remote computer the RDP connection will proceed to the next step. Or, in other words, the credential provider on the remote computer will not receive an incorrect set of login credentials from the client computer. 
- There could be some negotiations at this stage if the remote computer has a different user logged in at the time, than the one requested in the RDP connection. So provided that said negotiations were resolved in favor of a remote user.
- When the RDP connection is established, the remote computer will create two secure desktop sessions:
	
		- One session for the RDP connection. This is the desktop that will be displayed on the client computer that is connecting via RDP connection.
- Another session for the remote computer. This is the desktop that will be displayed on the remote computer's physical screen, usually a credential provider itself.
			
				The purpose of this secure desktop session is to obscure the user desktop that is now transmitted via RDP to the client computer. 
 Note that both sessions will have its own instance of the credential provider running in the remote computer. Thus, in a sense, there will be two instances of the credential provider running at the same time. 
- When the RDP session is disconnected, its desktop session will be closed, and the remote computer will have only the second session with the credential provider running.
- A user on the remote computer can now log in to the user desktop using its local credential provider.
Finally, if you want to retrieve the RDP session details of a connecting client computer from the remote computer, you can use the following technique:
//Get additional details about connecting 'client' computer during RDP session
LPTSTR ppBuffer = NULL;
DWORD dwcbBytesReturned = 0;
WTS_CLIENT_ADDRESS* pWTSCA = NULL;
if(WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
	WTS_CURRENT_SESSION, 
	WTSClientAddress, 
	&ppBuffer, 
	&dwcbBytesReturned))
{
	//Sanity check
	if(dwcbBytesReturned >= sizeof(WTS_CLIENT_ADDRESS))
	{
		pWTSCA = (WTS_CLIENT_ADDRESS*)ppBuffer;
		// Address family can be only:
		// AF_UNSPEC  = 0 (unspecified)
		// AF_INET    = 2 (internetwork: UDP, TCP, etc.)
		// AF_IPX     = AF_NS = 6 (IPX protocols: IPX, SPX, etc.)
		// AF_NETBIOS = 17 (NetBios-style addresses)
		//
		int nAddrFam = pWTSCA->AddressFamily;
		// The client local IP address is located in bytes 2, 3, 4, and 5.
		// The other bytes are not used.
		// If AddressFamily returns AF_UNSPEC, the first byte in Address
		// is initialized to zero.
		//
		wprintf(L"AddressFamily: %d, RemoteIP: %u.%u.%u.%u\n",
			nAddrFam,
			pWTSCA->Address[2],
			pWTSCA->Address[3],
			pWTSCA->Address[4],
			pWTSCA->Address[5]);
	}
}
if(ppBuffer)
{
	WTSFreeMemory(ppBuffer);
	ppBuffer = NULL;
}To determine if the RDP connection is on from the remote computer's side, you can use this approach.
Conclusion
This is probably the longest blog post that I've written to date. But I had to keep it all in one place to allow ease of search and reference. This post though is far from being a complete manual on writing a credential provider. But at least it has some useful notes that may come handy to developers.
If you feel like you have something to add to my overview, please leave a comment below.
And, if your organization needs me to code a credential provider for you, or if you are needing help with your existing credential provider project, don't hesitate to send me a private message below. We provide consulting and development services for businesses.
 
		














