Identity Server 4
Two years ago I created this draft. Today it is relevant to the client work that I am currently doing for another API project. Everything old is new again.
My client’s product helps customers manage collateralized loans. Customers asked for more openness in the product. To empower those customers the client wanted to create API endpoints. These endpoints required security.
Identity Server was an interesting option to protect access to various web API endpoints. I dug into researching it and created a proof of concept application. An Active Directory authenticated user would need a token to access the API endpoint.
My first attempt utilized the Resource Owner Password grant type. Since the user had already logged into a windows machine, I could reliably pass that username to the token service and get an authorization token. At a demonstration, skeptics greeted me. They worried, and rightly so, that there was a possibility of this being spoofed for nefarious intents. So I went back to the drawing board.
How do I authenticate a “native app” without asking for a password?
Think, think, think - in a Pooh Bear voice
After a few failed attempts at google-fu, I found the magic incantation that revealed these gems:
- OAuth 2.0 for Native Apps
- Proof Key for Code Exchange by OAuth Public Clients
- oauth apps for windows
I’d found the tools that I needed to allay fears of spoofing and interception. The Internet Engineering Task Force (IETF) recommended allowing a user’s web browser to handle the authentication. I thought I was home free. I set about to integrate this grant type and the PKCE into my proof of concept application.
At the end of the week, I demonstrated browser-based authentication and authorization. I fired up my identity server. Next, I started the secured API endpoint. Finally my surrogate desktop application with the magic “authenticate” button. When I clicked the button the default web browser opened as intended. The browser displayed a page with different authentication options.
Choosing the “Windows” button started a complex dance. The web server and application negotiated the authentication mechanism. When the Kerberos ticket authentication handshake resolved the desktop application received an authentication code. The application traded the code for an authorization token. Authorization token in hand, a web request asked the endpoint for meaningful data. That data soon flooded the display. Ta-da! OAuth token authentication from a desktop as recommended by the IETF.
The skeptics were not impressed. But why does the user need to click a button? And why do we need to show them the consent screen? Can’t we just log them in without any of that interaction?
I reasoned that the browser was already perfectly equipped to handle the redirects. Authentication negotiation and getting a Kerberos ticket on a desktop application without asking the user for a password was impossible for a reason. No one wanted to ask the user for a password. The less we and the application knew about the user account the better it would be for us.
My marching orders became don’t make the user click anything and don’t show the grant consent screen. The latter request was by far the easier of the two. The client configuration settings for Identity Server make this easy:
new Client
{
RequireConsent = false
}
That’s all it took to hide the consent screen.
Skipping the login button was trickier. I read through a variety of issues on the Identity Server Github project. Eventually I stumbled upon a clue that resulted in a server-side client definition that looks like this:
new Client
{
ClientName = "DesktopClient",
ClientId = "native.hybrid",
AllowedGrantTypes = GrantTypes.Code,
RefreshTokenUsage = TokenUsage.ReUse,
RequirePkce = true,
RequireClientSecret = false,
RequireConsent = false,
RedirectUris = { "http://localhost:5200/"},
AllowRememberConsent = true,
UpdateAccessTokenClaimsOnRefresh = true,
AllowedScopes =
{
"SecuredApi",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
EnableLocalLogin = false,
IdentityProviderRestrictions = new List<string> {"Windows"}
}
Adding the IdentityProviderRestrictions enabled me to skip past showing the user a login screen and process the windows login immediately. Following the native application sample, I could open the browser window to the Identity Server, completed the authentication negotiation, grab the returned authorization token and display a message confirm authentication. With a bit more tinkering I was able to style the authentication message to match the theme of the desktop application and keep the feel consistent.
This was a fun and challenging learning experiment. I had to draw from disparate sources and piece a solution together. I learned a bunch from reading OAuth 2 in Action and reasoning about their Node JS examples.
Even with the well-done documentation for Identity Server and its various configuration options, there was still much trial and error fiddling with server-side and client-side configuration settings to learn which setting did what and which settings needed to be an exact match. Thankfully I learned the scientific method long ago and only changed one option at a time so that I could reason reliably about the cause and the effect.
What thought experiments have grabbed your attention lately?