I am writing a windows desktop application with External Authentication(Google, Facebook) in C#.
I’m using HttpListener to allow a user to get Barer token by External Authentication Service with ASP.NET Web API, but administrator privileges are required for that and I want run without admin mode.
My reference was Sample Desktop Application for Windows.
Is this the best practice for external authentication provider from C#? Or is there another way to do that?
This is my code to get Barer token by external provider:
public static async Task<string> RequestExternalAccessToken(string provider)
{
// Creates a redirect URI using an available port on the loopback address.
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
// Creates an HttpListener to listen for requests on that redirect URI.
var http = new HttpListener();
http.Prefixes.Add(redirectURI);
http.Start();
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = Properties.Settings.Default.Server
+ "/api/Account/ExternalLogin?provider="
+ provider
+ "&response_type=token&client_id=desktop"
+ "&redirect_uri="
+ redirectURI + "?";
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var context = await http.GetContextAsync();
// Sends an HTTP response to the browser.
var response = context.Response;
string responseString = string.Format("<html><head></head><body></body></html>");
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
responseOutput.Close();
http.Stop();
Console.WriteLine("HTTP server stopped.");
});
// Checks for errors.
if (context.Request.QueryString.Get("access_token") == null)
{
throw new ApplicationException("Error connecting to server");
}
var externalToken = context.Request.QueryString.Get("access_token");
var path = "/api/Account/GetAccessToken";
var client = new RestClient(Properties.Settings.Default.Server + path);
RestRequest request = new RestRequest() { Method = Method.GET };
request.AddParameter("provider", provider);
request.AddParameter("AccessToken", externalToken);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
var clientResponse = client.Execute(request);
if (clientResponse.StatusCode == HttpStatusCode.OK)
{
var responseObject = JsonConvert.DeserializeObject<dynamic>(clientResponse.Content);
return responseObject.access_token;
}
else
{
throw new ApplicationException("Error connecting to server", clientResponse.ErrorException);
}
}
2
Answers
I don’t know about Facebook, but usually (I am experienced with Google OAuth2 and Azure AD as well as Azure AD B2C), the authentication provider allows you to use a custom URI scheme for the authentication callback, something like
badcompany://auth
To acquire an authentication token I ended up implementing the following scheme (All code is presented without warranty and not to be copied thoughtlessly.)
1. Register an URI-handler when the app is started
You can register an URI-Handler by creating a key in the
HKEY_CURRENT_USER/Software/Classes
(hence no admin privileges needed) key in the Windows registrybadcompany
in our caseURL Protocol
DefaultIcon
for the icon (actually I do not know whether this is necessary), I used the path of the current executableshell/open/command
, whose default value determines the path of the command to execute when the URI is tried to be opened, **please note*, that the"%1"
is necessary to pass the URI to the executable2. Open a pipe for IPC
Since you’ll have to pass messages from one instance of your program to another, you’ll have to open a named pipe that can be used for that purpose.
I called this code in a loop in a background
Task
3. Make sure that the application is started only once
This can be acquired using a
Mutex
.When the mutex has been acquired by another instance, the application is not fully started, but
4. Handle being called with the authentication redirect URI
The URI is sent to the first instance with
To add to Paul’s excellent answer:
If it helps there is some stuff on my blog about this – including a Nodejs / Electron sample you can run from here to see what a finished solution looks like.