Building a federated authentication client with OpenID Connect
Dominick and I have been working hard at implementing OpenID Connect in Thinktecture IdentityServer. Dominick has recently completed the authorization server and user profile endpoint bits. We also just recently completed a sample for a basic profile client (meaning server-side web application, or code flow client).
Our approach was to provide a very simple library to allow a client application to authenticate users without knowing all the protocol details. We built a http module (inspired by WIF’s FAM) that will implement all the necessary protocol details and once the user’s identity is established we then use the SAM to log the user into the application. To use the library all the client application needs to do is register the OpenIdConnectAuthenticationModule http module (as well as the SAM) and provide some configuration settings. Here are the steps:
In web.config, register the http modules:
<system.webServer> <modules> <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <add name="OpenIdConnectAuthenticationModule" type="Thinktecture.IdentityModel.Oidc.OpenIdConnectAuthenticationModule" /> </modules> </system.webServer>
Then configure the OpenIdConnectAuthenticationModule, either via a web.config entry:
<configSections> <section name="oidcClient" type="Thinktecture.IdentityModel.Oidc.OidcClientConfigurationSection, Thinktecture.IdentityModel.Oidc" /> </configSections> <oidcClient clientId="oidccode" clientSecret="secret" scope="profile offline_access" issuerName="http://identityserver.v2.thinktecture.com/samples" signingCertificate="CN=idsrv.local" appRelativeCallbackUrl="~/oidccallback" callUserInfoEndpoint="true" authorizeErrorRedirectUrl="~/Home/OidcError" > <endpoints authorize="https://idsrv.local/issue/oidc/authorize" token="https://idsrv.local/issue/oidc/token" userInfo="https://idsrv.local/issue/oidc/userinfo" /> </oidcClient>
Or in code in global.asax (or a combination of the two):
protected void Application_Start() { OidcClientConfigurationSection.Instance.ClientId = "MyClientId"; OidcClientConfigurationSection.Instance.ClientId = "MySecret"; }
The settings needed are:
ClientID: Client application’s identifier as configured in the OpenID Connect Authorization Server.
Client Secret: Client application’s secret as configured in the OpenID Connect Authorization Server.
Scope: Whitespace delimited list of OpenID Connect scopes being requested.
Issuer Name: Identifier of the OpenID Connect Authorization Server identifier.
Signing Certificate: Certificate subject distinguished name used to lookup the signing certificate in the “Trusted People” machine certificate store.
App Relative Callback Url (optional): Application relative URL for the OpenID Connect callback. Defaults to “~/oidccallback”. Change if you prefer a different URL.
Call UserInfo Endpoint (optional): Flag indicates if the client app needs the user profile identity information. Use “false’ if only the “sub” claim is needed. Defaults to “true”.
Authorize Error Redirect Url (optional): Application relative URL to show to the user when there is an authorization error from the OpenID Connect Authorization Server.
Authorize Endpoint: Authorization URL as provided by the OpenID Connect Authorization Server.
Token Endpoint: Token URL as provided by the OpenID Connect Authorization Server.
User Info Endpoint: User Profile URL as provided by the OpenID Connect Authorization Server.
The OpenIdConnectAuthenticationModule does two things:
1) It looks for 401 (unauthorized) http status codes from the application and initiates the OpenID Connect protocol by redirecting to the Authorization Server’s authorize endpoint.
2) It waits for the OpenID Connect Authorization Server to then call back into the callback URL to provide the client application with the authorization response. The OpenIdConnectAuthenticationModule will then continue with the rest of the OpenID Connect protocol (which involves calling back to the user info endpoint). Ultimately once the protocol completes the OpenIdConnectAuthenticationModule uses the SAM to issue a session token. Upon the next request into the application the user identity will be accessible via ClaimsPrincipal.Current.
The last bit of extensibility is that the OpenIdConnectAuthenticationModule raises events as it’s doing its processing. You can handle them in global.asax as such:
void OpenIdConnectAuthenticationModule_AuthorizeResponse(object sender, AuthorizeResponseEventArgs args) { if (args.Response.IsError) { args.Cancel = true; args.RedirectUrl = "~/error"; } } void OpenIdConnectAuthenticationModule_TokenResponse(object sender, TokenResponseEventArgs args) { } void OpenIdConnectAuthenticationModule_IdentityTokenValidated(object sender, IdentityTokenValidatedEventArgs args) { } void OpenIdConnectAuthenticationModule_UserInfoClaimsReceived(object sender, UserInfoClaimsReceivedEventArgs args) { } void OpenIdConnectAuthenticationModule_SessionSecurityTokenCreated(object sender, SessionTokenCreatedEventArgs args) { } void OpenIdConnectAuthenticationModule_SignedIn(object sender, EventArgs args) { } void OpenIdConnectAuthenticationModule_Error(object sender, ErrorEventArgs args) { }
The events raised by the OpenIdConnectAuthenticationModule are:
AuthorizeResponse: Called when the authorize endpoint in the client application is called by the Authorization Server. The OidcAuthorizeResponse is passed in as a property of the event args and indicates the outcome of authorization.
TokenResponse: Called after the token endpoint is invoked. The OidcTokenResponse is passed in as a property of the event args.
IdentityTokenValidated: Called after the identity token in the OidcTokenResponse is validated. The claims from the identity token are passed in as a property of the event args.
UserInfoClaimsReceived: Called after the user profile endpoint is invoked. The claims from the user profile response are passed in as a property of the event args.
SessionSecurityTokenCreated: Called after the SAM SessionSecurityToken is created from the user’s claims.
SignedIn: Called after the SAM SessionSecurityToken is written out as a cookie.
Error: Only invoked if there is an unexpected exception during the processing in the OpenIdConnectAuthenticationModule.
Many of the event args passed to the event handlers also contain Cancel and RedirectUrl properties. Cancel allows the event handler to force the OpenIdConnectAuthenticationModule to stop processing and the RedirectUrl is the URL the user is then redirected to.
As of now, the OpenIdConnectAuthenticationModule is a sample on github. We are seeking feedback on features and improvements. Once it’s got some more mileage, we plan to incorporate the OpenIdConnectAuthenticationModule into IdentityModel.
Reblogged this on http://www.leastprivilege.com.
Are you guys planning on working with the OIDC implicit flow as well?
Doug — do you mean for a client application? This would be all JavaScript (essentially). I just assumed there were already OIDC JS libraries out there for the implicit flow.
Sorry, I am still trying to wrap my head around OIDC. So there isn’t anything different on the ID server side that would be required to support the implicit flow?
Ok, I see. Yes, the authorization server needs to support this flow. We’ll be adding it to IdentityServer at some point.
Hello Brock,
I’m in the difficult position of choosing which way to go with regards to AuthN and AuthZ on a project I am working on. I like the home realm discovery stuff you get with WS-* and SAML etc – but when it comes to OIDC, I’m not sure how it could work…
Can you ‘chain’ identity providers in that same way that you can using the WS-Federation stuff and HRD? E.g. Facebook–>MyIdServ->MyWebAPI
I’m not sure that its possible – as which user info endpoint would you call if it was chained?
Or would each link in the chain call the user endpoint of the one infront of it to fully populate the token with all the claims of the incoming token provider?
Im confused!
Thanks for any help in advance,
Matt
OIDC, doesn’t have the concept of HRD. And OIDC with a federated identity doesn’t make much sense (if you need the full profile) because if your identity is from a third-party then how would your IdP know the profile info from the third-party’s DB? So yea, what you choose will depends on many factors about your app, but maybe WS-Fed (given its simplicity) might be the way to go.
Hello Brock,
is the above mentioned client still current or is it already integrated in the new Identity server? I mean – are this really the currrent bits?
Regards
Dirk
Microsoft has a beta of an OIDC middleware that will be released with Katana v3 later this summer. That’s what’s I’d suggest using now.
The Microsoft OIDC middleware fits in better as OWIN middleware, but only supports Hybrid flow. Would it be difficult to turn the Thinktecture.IdentityModel.Oidc into OWIN middleware, thereby supporting the other OpenID Connect flows (especially AuthorizationCode)?
Thank you, Christiaan
You are not forced to use hybrid when using the OIDC middleware. You can just use implicit.