Connect a WinForms Data Grid to an ASP.NET Core WebAPI Service Using EF Core — Authorization Code Flow
NOTE: Our Connect a WinForms Data Grid to an ASP.NET Core WebAPI Service — Authenticate Users and Protect Data example adds a user login and permission-based access control feature using Resource Owner Password Credentials (ROPC). This approach, while familiar in traditional client applications, requires the application to collect and transmit user credentials, which creates security-related issues.
To enhance security, this specific example leverages Authorization Code Flow (designed for applications that need to authenticate users without directly handling user credentials). Authorization Code Flow ensures that credentials remain within the authentication service, reducing the risk of misuse.
Use the following steps to configure Keycloak for Authorization Code Flow:
- Navigate to Client scopes in your realm settings.
- Check if the openid scope is listed. If missing, click Create client scope and do the following:
- Enter openid as the name.
- Select OpenID Connect as the protocol.
- Set type to Default.
- Save the entry.
- Go to Clients and click Create client.
- Select OpenID Connect as the client type.
- Enter a Client ID ('appAuthFlow1' in this example).
- Click Save.
- Open the Capability config page.
- Locate the Authentication flow section.
- Uncheck Direct access grant (it may be enabled by default).
- Ensure Standard flow remains active (required for the Authorization Code Flow).
- Open the Login settings page.
- Add at least one valid Redirect URI. For this sample, use:
winappdemo://*.
Refer to the following step-by-step tutorial to run the example: Getting Started.
Register a custom protocol prefix in Windows. This allows Windows to recognize the URL format and launch your application when redirection occurs (details that are passed through by the URL are supplied as command-line arguments).
The RegisterProtocol registers a custom protocol in the registry:
static void RegisterProtocol()
{
// The name of the custom protocol must correspond to your Valid URI configuration on the Keycloak side.
string customProtocol = “winappdemo”;
string applicationPath = Application.ExecutablePath;
var keyPath = $@“Software\Classes\{customProtocol}”;
using (var key = Registry.CurrentUser.CreateSubKey(keyPath, true))
{
if (key == null)
{
throw new Exception($”Registry key can’t be written: {keyPath}”);
}
key.SetValue(string.Empty, “URL:” + customProtocol);
key.SetValue(“URL Protocol”, string.Empty);
using (var commandKey = key.CreateSubKey(@“shell\open\command”))
{
commandKey.SetValue(string.Empty, $”{applicationPath} %1”);
}
}
}This example implements a mechanism that uses a named pipe to send incoming protocol information to a running instance of the same application.
On startup, if no parameters are found on the command line, the application attempts to start a listener for the named pipe. The HandleProtocolMessage method passes the message to DataServiceClient:
private const string pipeName = “WinAppDemoProtocolMessagePipe”;
…
static void StartProtocolMessageListener()
{
Task.Run(async () =>
{
while (true)
{
using var server = new NamedPipeServerStream(pipeName, PipeDirection.In);
server.WaitForConnection();
using var reader = new StreamReader(server);
var msg = reader.ReadToEnd();
await HandleProtocolMessage(msg);
}
});
}
static async Task HandleProtocolMessage(string msg)
{
Console.WriteLine($”Handling protocol message: ‘{msg}’”);
await DataServiceClient.AcceptProtocolUrl(msg);
}If a command-line parameter is detected, the application tries to pass details to an existing instance.
static void SendProtocolMessage(string msg)
{
using var client = new NamedPipeClientStream(“.”, pipeName, PipeDirection.Out);
// The `Connect` call uses a short timeout. If no running instance is found, there is an error in the process.
client.Connect(500);
using var writer = new StreamWriter(client) { AutoFlush = true };
writer.Write(msg);
}Login logic is divided into two parts:
- A login method that calls
DataServiceClient. - An event handler that listens for login status changes in
DataServiceClientand updates the UI accordingly.
Authorization Code Flow begins in the DataServiceClient.LogIn method. This implementation uses Url.Combine from the Flurl library to construct the required URL.
Process.Start initiates the login, which opens the default system browser and navigates to the Keycloak login page.
var url = Url.Combine(authUrl, “realms”, realm, “protocol”, “openid-connect”, “auth”)
.SetQueryParams(
new
{
response_type = “code”,
client_id = clientId,
redirect_uri = redirectUri,
scope = “openid profile email”,
}
);
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });Once the user successfully logs in, Keycloak redirects to the registered URI, which triggers a second instance of the application. This instance calls DataServiceClient.AcceptProtocolUrl to process the authorization response.
public static async Task AcceptProtocolUrl(string protocolUrlString)
{
var protocolUrl = new Url(protocolUrlString);
if (
protocolUrl.QueryParams.TryGetFirst(“code”, out object codeObject)
&& codeObject is string code
)
{
CheckSettings([“authUrl”, “realm”, “clientId”, “redirectUri”]);
var content = new FormUrlEncodedContent(
new Dictionary<string, string>
{
{ “grant_type”, “authorization_code” },
{ “client_id”, clientId! },
{ “code”, code },
{ “redirect_uri”, redirectUri! },
}
);
var url = Url.Combine(
authUrl,
“realms”,
realm,
“protocol”,
“openid-connect”,
“token”
);
var response = await bareHttpClient.PostAsync(url, content);
…The DataServiceClient.GetTokens method obtains the following tokens:
id_tokenaccess_tokenrefresh_tokenexpires_in
static (
string? id_token,
string? access_token,
string? refresh_token,
int? expires_in
) GetTokens(string jsonString)
{
var node = JsonNode.Parse(jsonString);
if (node == null)
return (null, null, null, null);
else
return (
node[“id_token”]?.GetValue<string>(),
node[“access_token”]?.GetValue<string>(),
node[“refresh_token”]?.GetValue<string>(),
node[“expires_in”]?.GetValue<int>()
);
}Once the login process starts, the user is redirected to the Keycloak authentication page in a browser. The demo includes two test accounts:
- Username:
reader| Password:reader - Username:
writer| Password:writer
After successful authentication, Keycloak redirects the user back to the WinForms application, completing the authorization process.
(you will be redirected to DevExpress.com to submit your response)
