The most relevant spec for authentication and API access for native apps has been recently updated.
If you are “that kind of person” that enjoys looking at diffs of pre-release RFCs – you would have spotted a new way of dealing with the system browser for desktop operating systems (e.g. Windows or MacOS).
Quoting section 7.3:
“More applicable to desktop operating systems, some environments allow apps to create a local HTTP listener on a random port, and receive URI redirects that way. This is an acceptable redirect URI choice for native apps on compatible platforms.”
IOW – your application launches a local “web server”, starts the system browser with a local redirect URI and waits for the response to come back (either a code or an error). This is much easier than trying to fiddle with custom URL monikers and such on desktop operating systems.
William Denniss – one of the authors of the above spec and the corresponding reference implementations – also created a couple of samples that show the usage of that technique for Windows desktop apps.
Inspired by that I, created a sample showing how to do OpenID Connect authentication from a console application using IdentityModel.OidcClient.
In a nutshell – it works like this:
Open a local listener
// create a redirect URI using an available port on the loopback address. string redirectUri = string.Format("http://127.0.0.1:7890/"); Console.WriteLine("redirect URI: " + redirectUri); // create an HttpListener to listen for requests on that redirect URI. var http = new HttpListener(); http.Prefixes.Add(redirectUri); Console.WriteLine("Listening.."); http.Start();
Construct the start URL, open the system browser and wait for a response
var options = new OidcClientOptions( "https://demo.identityserver.io", "native.code", "secret", "openid profile api", redirectUri); options.Style = OidcClientOptions.AuthenticationStyle.AuthorizationCode; var client = new OidcClient(options); var state = await client.PrepareLoginAsync(); Console.WriteLine($"Start URL: {state.StartUrl}"); // open system browser to start authentication Process.Start(state.StartUrl); // wait for the authorization response. var context = await http.GetContextAsync();
Process the response and access the claims and tokens
var result = await client.ValidateResponseAsync(context.Request.Url.AbsoluteUri, state); if (result.Success) { Console.WriteLine("\n\nClaims:"); foreach (var claim in result.Claims) { Console.WriteLine("{0}: {1}", claim.Type, claim.Value); } Console.WriteLine(); Console.WriteLine("Access token:\n{0}", result.AccessToken); if (!string.IsNullOrWhiteSpace(result.RefreshToken)) { Console.WriteLine("Refresh token:\n{0}", result.RefreshToken); } } else { Console.WriteLine("\n\nError:\n{0}", result.Error); } http.Stop();
Sample can be found here – have fun ;)
Clever method. In my case, I had a native windows app (winforms) and did not want to open the system browser because I have RequireConsent = false for this client on the IdentityServer end. So, I utilized CefSharp.OffScreen to launch a headless browser with the StartUrl (instead of the System browser).
Something to add for anyone else that wants to use Hybrid Flow with this approach…
I believe you will need to set the UseFormPost property of the OidcClientOptions to true and then read the form data out of the request when it is received (after awating GetContextAsync). If you don’t set the UseFormPost to true, then the response_mode defaults to Fragment. Apparently, Fragment data is not sent to the Server (the HttpListener in this case).
Hybrid would be preferred. Please send a PR for the sample.
Hello Dominick – I made the changes to the ConsoleSystemBrowser sample and will submit a PR, but I am having an issue testing the sample against the https://demo.identityserver.io authority. During the call to ValidateResponseAsync, I eventually receive some HTML back from the call to IdentityModel.Client.TokenClient’s RequestAsync method. The response comes back with an OK status code, but the content is HTML that includes text stating “You have browsed from an unrecognized IP address. The proxy therefore denied access to this site or page”.
When I clone demo.identityserver.io and run it locally everything works OK. Any suggestions before I proceed?
That’s weird – I don’t think this is coming for our backend. Anyways – send the PR and I will test it on my side.
[Done](https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/pull/3). Thanks,
thanks! will have a look
Hi! Thanks for sharing and code sample. But I have question. Is it a good (valid) idea to use custom URI scheme for receiving auth code? Something like that used for native mobile application. So with each installation of application I will register custom “my.application” scheme and then after successful login user will be redirected to this scheme with auth code.
Yes – check the spec
https://tools.ietf.org/html/draft-ietf-oauth-native-apps-03
Thanks!