IdentityServer / IdentityServer3

OpenID Connect Provider and OAuth 2.0 Authorization Server Framework for ASP.NET 4.x/Katana
https://identityserver.github.io/Documentation/
Apache License 2.0
2.01k stars 764 forks source link

How to replicate in code what happens when UseOpenIdConnectAuthentication authenticates to IdentityServer #1245

Closed kkfrosty closed 9 years ago

kkfrosty commented 9 years ago

Note we went down the path of WsFederation but that doesn't appear to support mixed mode like we need it too. We've looked at a MVC mixed mode sample online but have had no luck getting it to work the way we need it. Besides, the questions we have will have to be figured out whether using that method or ours.

I'm going to create a details of what we're doing in case anything is unclear first. If you are familiar with UseOpenIdConnectAuthentication and how it's configured for use with IdentityServer, reading the details may be unnessecary.

After that, I'll post a Question section for what we're trying to accomplish.

Details Our application is commercial. We are building it as a hybrid. Primarily, it's being built to host in the cloud and allow customers to utilize purchase licensing to use it.

Our application is web based. We build the UI with MVC in one solution. Then all of our backend is built in separate solutions. The backend is hosted with WCF and WebApi services. We chose IdentityServer to utilize JWT tokens for authorization between communication between the layers.

Thus we have our security database tables we use for authentication and authorization. We used the IdentityServer Master download and hosted it with IIS. We've built our custom User & claims providers and it works great. User opens the MVC UI, they get directed to the login page on the IdentityServer, sign in, redirected back to MVC application where the AuthorizedCodeRecieved function of UseOpenIdConnectAuthentication runs, assembles the users claims including their access token, refresh token, id token and expiration.

However, now we have to build the app backwards compatible for our onsite customers. Which means, we have to be able to allow them to install the application on their local servers.

For those customers, most are windows environments with AD and they do not want internal users to get prompted for a login. So what we do is associate user accounts in our database with their windows accounts.

In IIS for the MVC UI web app, we've created a .aspx page called WinAuth.Aspx. In IIS we set authentication on that page only to Windows Authentication. We have an app.setting to enable windows authentication. If true, then we load a our custom AuthorizeAttribute in FilterConfig.cs. In it, it checks to see if a user is authenticated, if not then they get redirected to our WinAuth.aspx page which only allows Windows Authentication

This is simple and easy to follow and maintain.

In the WinAuth.aspx code behind, we make sure the user is Authenticated and has a Windows UserName which we extract.

We Then user UserManager.FindByNameAsync to get the user which works fine.

We know we can use IUsers CreateIdentityAsync to generate a ClaimsIdentity.

Next we know we could use identity's RequestResourceOwnerPasswordAsync to get Claims and populate the Identity Created with CreateIdentityAsync.

However, that is not creating a refresh token or anything of the sort. So now to the questions.

Questions 1) In code if we have a username and password to pass to Identity server, is there anyway possible to simulate in code what happens when a user types in their credentials and clicks login on the Identity login page?

What appears to happen when users do it manually, is they get redirected back to our MVC application and then the AuthorizationCodeRecieved func which is defined in the Notifications of app.UseOpenIdConnectAuthentication runs.

In this code, claims are retrieved, a call to get UserClientInfo is made. More claims are added from this call.

Once that is complete, the OAuth2Client is used to make a call to RequestAuthorizationCodeAsync to get an Access token, set expire at and also to get a refresh token and id token.

To recap, is there a way in code we could replicate the authentication process that occurs when a user is redirected to the Identity Login page, enters their credentials and gets redirected back to the application?

2) If question 1 is not possible, we're thinking of trying to just run the code we have defined in the AuthorizationCodeRecieved func. However, when making a call to UserInfoClient, we're not sure how to best go about getting the accesstoken which in the AuthorizationCodeReceived func comes from ProtocolMessage.Accesstoken.

Additionally, in making a call to RequestAuthorizationCodeAsync we're not sure how to go about getting the AuthorizedCodeReceivedNotification.Code or the ProtocolMessage.IdToken

If need be, I can post our Startup.ConfigureAuth code which contains app.UseIdConnectAuthentication. However, starting out in an effort to not bloat this post, I'm leaving it out. What we're using there is what we got from the Identity samples.

kkfrosty commented 9 years ago

Maybe this makes it simpler, in code behind we have a user name a password for our windows users. We've integrated with Identity Server 3 pretty much identically to what's found in this article.

http://leastprivilege.com/2014/10/10/openid-connect-hybrid-flow-and-identityserver-v3/ So with a user name and password, how do we go about getting an authorization code and Access Token and use it as the article does to make a call to get UserInfo. Then make the call to RequestAuthorizationCodeAsync so we can obtain Access Token, ExpiresIn, RefreshToken and IdToken.

After that I'm hoping we can use HttpContext.Current.GetOwinContext to possibly set the AuthenticationTicket.

Basically in code for authenticated windows users, we want to duplicate the processes that take place via UseOpenIdConnectAuthentication() and when everything is finished, everything functions just as if the user was redirected to the Identity Login page, authenticated was redirected back and the UseOpenIdConnectAuthentication. I guess with my limited knowledge write now, we can't just take the username and password, make a call to RequestResourceOwnerPasswordAsync() and only have an AccessToken.

brockallen commented 9 years ago

With a uid/pwd you can use the resource owner password flow and get an access token. With said access token you can invoke the user profile API (assuming you've requested the right scopes). Then in your app you can issue a cookie or whatever you want to log them in.

kkfrosty commented 9 years ago

Thank you Brock,

We've been working off your last recommendation. We take the UserName/Password, we make a call to OAuth2Client.RequestResourceOwnerPasswordAsync().

We get back an AccessToken. We use that to then make a call to UserInfoClient.GetAsync();

Unfortunately all we're getting back is 3 standard claims. In our custom Claims provider we use in Identity Server, we use the ClaimType of AuthorizationDecision to store our SecurityObject and permission guids. (These are unique to our environments.)

Since, I've started digging through the OpenId spec and from what I understand, section 5.5 Requesting Claims using the "claims" Request Parameter pertains to us.

A couple of questions:

1) In case I'm missing something, is there anyway to make a call to UserClientInfo with parameters such as it will return all of the claims associated with a user? I'm passing in the following scopes to RequestResourceOwnerPasswordAsync -

var _response = await _tokenClient.RequestResourceOwnerPasswordAsync(_slUser.UserName, _slUser.PasswordLegacy, "all_claims openid email profile offline_access SLWindowsAuthentication");

However all I get back are 3 name related claims.

2) In case question 1 isn't possible, 5.5 indicates there is claims parameter. It is detectable by the claims_parameter_supported discovery result which I don't see. If the OP doesn't support this parameter and the RP uses it, the OP should return a set of claims to the RP that it believes would be useful to the RP.

3) As you can see I pass in the profile scope and for our predefined scope we use is SLWindowsAuthentication. IN the scope instantiation in IDS 3, we were trying to add scope claims, however, there isn't a ClaimType defined for AuthorizationDecision in Core Constants. We tried adding something but still no luck in getting back the actual claims we need. Here is what we've tried for Scope on IDS 3. new Scope { Enabled = true, Name = "SLWindowsAuthentication", Description = "SmartLogix Windows Authentication Scope", Type = ScopeType.Resource,

                    Claims = new List<ScopeClaim>
                    {
                        new ScopeClaim(Constants.ClaimTypes.AuthorizationDecision),  <-- Manually created this.
                        new ScopeClaim(Constants.ClaimTypes.Role)

}

                }

Not sure what we're missing or have to do to get a users claims returned. Also, if we have to resort to what is defined in section 5.5 of the OpenId spec, how would we go about trying to do this with IDS?

Thanks

kkfrosty commented 9 years ago

I've had zero luck with Identity Server using UserClientInfo. What's frustrating about the fact is in the UserInfoEndpointController the following code gets the entire identity with all the claims.

var tokenUsageResult = await _tokenUsageValidator.ValidateAsync(request);

Yet, it goes through a lot of code to basically return nothing.

Without any decent documentation, I've been concerned about doing something that would invalidate the structure of this code, thus I've been researching through the OpenId spec for other options.

Two I've found are: 1) Looking at discovery I found there is An AccessTokenValidation endpoint. I've yet to find a CLient endpoint to call this. However, if you append your accesstoken to this, you get back all your userinfo including claims. https://localhost/IdentityApis/core/connect/accesstokenvalidation?token=

2) I borrowed from code in a BearerTokenMessageInspector class I got from the Internet to help with configuring WCF to utilize JWT's. (Here is a series of blogs with sample code.) https://kkfrost.wordpress.com/2015/03/05/identity-server-v3-lob-part-2-web-api-wcf-service-integration/

I'm still left wondering why we can't seem to get UserInfo to work the way we expected. We're still left with trying to populate the built in ClaimsIdentity so it's identical to how it gets created when using UseOpenIdConnectAuthentication

leastprivilege commented 9 years ago

I can't be bothered to read through all that - do you have a concise description what you want to do - and what you think does not work?

kkfrosty commented 9 years ago

We have to provide a way for Mixed Mode authentication. When enabled, code to try and get a windows UserName, look it up to see if there are credentials associated. If so, manually authenticate the user.

As I've pointed out numerous times, the end result is we need to end up with an Identity the same as what happens through the process of UseOpenIdConnectAuthentication.

We had everything to the point of getting claims back. You mentioned use the User Profile API. That's kind of terse so we deduced you were trying to say use the UserInfoClient.

However, this only returns a minimum set of standard claims. 5.5 in the OpenId specs starts talking about how to get more than standard claims but it doesn't appear Identity Server supports what's defined in 5.5.

What doesn't make sense if you debug the GetUserInfo endpoint, _tokenUsageValidator.ValidateAccessTokenAsync returns all the tokens a user has, or at least in our case. So there is no need to for all the other calls that end up only returning a standard set of claims.

You have an api endpoint https://localhost/IdentityApis/core/connect/accesstokenvalidation which basically does what we need. Is there any assembly wrapper for making this call, we haven't found it.

Is there any other possible way to get user claims using an access token to where you get everything using Identity Server and/or it's API's.

leastprivilege commented 9 years ago

UserInfo takes an access token, validates it and returns the claims from the userstore that are associated with the scopes inside the access token.

The access token validation endpoint takes an access token, validates it and returns the claims inside the token.

Two different things.

and no - we don't have a wrapper around making a POST with a token as a query string. The wrapper is called HttpClient.