Identity Server 4 won't use custom Authentication Handler - c#

I have a working AuthenticationHandler<> for MVC Core and it works beautifully. Now I want to re-use the whole thing for Identity Server 4 but the framework seems to work against me.
I have added
builder.Services.AddAuthentication().AddScheme<MyWebsiteOptions, MyWebsiteAuthenticationHandler<TIdentity, MyWebsiteUser>>(CredentialStoreConstants.SCHEMA_NAME, x => { x.ConnectionString = options.ConnectionString; });
And like I said it works 100% for the MVC Core-part. Now, I cannot access IdentityServer4 endpoints like /connect/authorize/callback with it. I have already read the Identity Server 4 documentation over and over again, but somehow I am missing some key thing here.
Does anyone has an idea? I am sure I am not the first person to run into this.

The Identityserver Endpoints are just that, plain endpoints implemented as a middleware for your pipeline.
The relevant files on the Identityserver4 are:
The Middleware: https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Hosting/IdentityServerMiddleware.cs
The Endpoint Router (and its interface): https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Hosting/EndpointRouter.cs
Endpoint Implementation (token endpoint for instance): https://github.com/IdentityServer/IdentityServer4/blob/master/src/IdentityServer4/src/Endpoints/TokenEndpoint.cs
This means they're out of MVC so you can't use your AuthenticationHandler with them
HINT: If you read all the documentation and the answer is not there, its time to dive into the code: https://github.com/IdentityServer/IdentityServer4

Answer. It was more obvious than I imagined it to be.
For the standard .NET Core Authentication, a succesfull pass at AuthenticationHandler<> in not enough
If you have a custom usertype and custom login flow, you also need to do something like this after you succesfully verified the credentials of said custom usertype.
At one point, you need to retrieve the corresponding user from the database and into the rest of the MVC Core authentication flow.
So after something like AuthenticationHandler<>().Succeeded == true you need to do this:
var systemUser = await _userResolver.GetUserAsync(user.Email);
await _signInManager.SignInAsync(systemUser, true);
The last line is the most important as it initializes a correct MVC Core user-session

Related

PayPal oauth2 authorization and .net core

So I'm trying to set up PayPal sign-in within my .net core application. I'm using this 3rd party library to do so: https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
I'm building my application upon Umbraco CMS, and within the appropriate section I use the following:
builder.AddMemberExternalLogins(logins =>
{
logins.AddMemberLogin(
memberAuthenticationBuilder =>
{
memberAuthenticationBuilder.AddPaypal(
memberAuthenticationBuilder.SchemeForMembers(PayPalMemberExternalLoginProviderOptions.SchemeName),
options =>
{
options.ClientId = clientId;
options.ClientSecret = clientSecret;
});
});
});
Out of the box, this does not work. From reading around it seems that it seems the authorization endpoints are using the live values, and these are not appropriate for testing with Paypal's sandbox endpoints.
After reading the following thread: https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/198 I can see that this user had to update the endpoints manually to use different values:
.AddPaypal(options =>
{
options.ClientId = Startup.Configuration["Authentication:Paypal:ClientId"];
options.ClientSecret = Startup.Configuration["Authentication:Paypal:ClientSecret"];
options.AuthorizationEndpoint = "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize";
options.TokenEndpoint = "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice";
options.UserInformationEndpoint = "https://api.sandbox.paypal.com/v1/identity/openidconnect/userinfo?schema=openid";
})
Now once updating these values, the authorization seems to work. But I've found it to be very unreliable. On my personal dev machine it seems to allow me to authorize, but when testing with another colleague, he finds he is constantly logged out and sometimes it doesn't authorize at all.
When I look at the previous thread I can see that this was written in 2017, and I'm wondering if the endpoints have been updated or the process has been changed entirely by Paypal, but I can't find any documentation on this.
Basically I'm looking for the simplest way to integrate Paypal sign-in on my application. When I read Paypal's documentation (https://developer.paypal.com/docs/log-in-with-paypal/integrate/) I only see mention of refreshing the token, I don't see anything about an authorization endpoint or user information endpoint.
I'm getting quite confused and I'm wondering if anyone has successfully managed to set up a Paypal sign-in with a .net core application in 2022 and whether they have any sample code.
Any pointers would be greatly appreciated. Thanks.
That does seem like an old integration. I would advise following https://developer.paypal.com/docs/log-in-with-paypal/integrate/ from scratch, which can be implemented in any language for the eventual server-side calls. The initial client-side authorization is done in either the JS popup or using a redirect (JS is better)
After exchanging the resulting authorization_code for a refresh token, that token can then be used to obtain information about the user's account (or do API operations involving it), according to the scopes that were authorized.

IdentityServer4 - Using External Authentication

I am trying to implement an Authorization server using IdentityServer4, using the Hybrid Flow.
let's say url is : auth.company.com
To authenticate users, the company uses a simple .NET MVC login/password form.
url : client.company.com/login.html
My question is : how can i plug the authentication system into the IdentityServer4 ?
I've tried adding this :
Startup.cs / ConfigureServices()
services.AddIdentityServer(SetupIdentityServer)
private static void SetupIdentityServer(IdentityServerOptions options)
{
options.UserInteraction.LoginUrl = #"client.company.com/login.html";
options.UserInteraction.LoginReturnUrlParameter = "referrer";
}
But it resulted in too many redirections error between auth server and authentication server
Thank you
I just replied to another question very similar to this so this is a shameless copy and paste of that:
This will not work as the identity server needs to issue its own cookie once authentication has taken place. This cookie is what allows the authorise endpoint to know who is signed in.
The intention of this model is that authentication takes place on the IDP or it’s negotiated with an external provider via a suitable protocol. Therefore the appropriate approach in this case is to move the login UI into your identity server application. It’s entirely up to you exactly how that is done and where it gets it’s data from but it must be your identityserver4 that issues the cookie.

How can I protect controller actions in an MVC app that doesn't contain a db or user data?

I'm looking to understand the nitty gritty mechanics of authorization so I can devise a strategy for my situation.
My situation is that I am part of a distributed application. My part is an MVC5 application that basically just consists of a couple of controllers that return single page app views. So hit controller A and get back single page app A. Hit controller B and get single page app B. Etc. This application contains no database or user data. Some other application on a completely different website/server does. I want to ask that other application if a user is valid or have users ask the other application directly themselves and only allow access to my app views if the answer is yes. So, in essence, I want to protect my controllers based on the word of a remote application that contains an exposed api for login/user validation.
It has been suggested to me that token authentication is the way to go. It's a bit daunting with my lack of experience with it, but I've buried myself in some reading and video presentations. Here is my current, weak attempt at summarizing the task based on limited understanding. Please correct as needed:
An access token needs to be generated
Getting an access token is not part of the Account controller, it's part of OWIN middleware
The access token will be sent along with the requests for my contoller actions
My controller actions, decorated with the [Authorize] attribute, will parse the token and do the right thing
Questions:
Q1: Should I generate the token or should the other app - the one with the db and user data?
Q2: My controllers don't know anything about users. That data is in the other app. What specifically are the controllers parsing for under the hood in order to do the right thing? In essence, what specifically tells them, "yes, this request is OK. Return the view."
Q3: I started my project awhile back using a standard MVC5 project template that comes with VS2015 because I figured I'd be handling users/login etc. That turned out not to be the case. Am I going to have to go back and redo this project from scratch because that template doesn't fit this requirement or can I do some surgery on it and continue? For instance, I don't know if that template has all the OWIN stuff I need or maybe has too much extra junk (bloated Account controller, Entity Framework, etc.) to be easily transformed/maintained.
Q4: Is token authorization overkill here? Is there an easier way to keep unauthorized users from accessing my controller actions that makes more sense given the nature of the project?
Any insight would be appreciated.
Update: What I meant in Q2 was, at it's simplest, how does [Authorize] work? Details? I'm guessing I have to tell it how to work. For instance, a silly example to illustrate. If I wanted to tell a controller decorated with [Authorize] to let anyone in who has the username "fred", how and where would I do that? I'm not so much looking for code. I'm thinking conceptually. My app must know something about the tokens the other app (authenticating app) is genenerating. In general terms, what would I add to my MVC app to tell it how to decode those tokens? Where do I add it? Is there one standard place?
I think you are on the right track and are right about the steps you have mentioned. I will answer your questions based on what I understand:
Q1. The other application is the one that needs to authorize and generate a token (whatever be the authorization mechanism they use) and you should receive this token before showing your views. Since the data is coming from the other application , they have to give your controllers access to their data. This is why you need to ask the other application for the token/authorization. With a valid token got from the other application your application can send valid and authorized requests to their data.
Q2. What you can do from your side is to add a check as to whether the request for your action/view is coming from an authorized user. For this, you need to check if this request has a valid token.
Q3. I don't know what you mean by "template" here. But if you need to integrate your controllers to the other solution, you do need to know what the other solution does and what it offers in terns of authorization and of course the data. They should provide your application access to a public api for this purpose.
q4. THis is something the other application needs to do. From what I understand, I think you are only adding a web API to an existing system so I think you need to really know how you can integrate with the other application. They should have clear APIs that are public for you to do this to access their features and data.
Since you have not mentioned if this other application is something like a secure enterprise solution or a Google API (has public API ) it would be difficult to tell exactly what you can expect from the other application.
I think you would need to try JSON web tokens (JWT )
I have not used it myself though . stormpath.com/blog/token-auth-spa –
It is useful for authenticating if a request to your controller. Here is a similar question as you have (I think) and how JWT could solve it How to use JWT in MVC application for authentication and authorization? and https://www.codeproject.com/Articles/876870/Implement-OAuth-JSON-Web-Tokens-Authentication-in
You can override the AuthorizeAttribute like this : https://msdn.microsoft.com/en-us/library/ee707357(v=vs.91).aspx . Your authorization logic of checking for whichever tokens/auth mechanism you decide to can be added to this new action filter. Then add that new attribute to your actions. So if your custom authorization attribute when overriding looks like this:
public class RestrictAccessToAssignedManagers : AuthorizationAttribute
Then your action would have the attribute decoration like this:
[RestrictAccessToAssignedManagers]
public ActionResult ShowallAssignees(int id)
Found a good article which could also be of help - https://blogs.msdn.microsoft.com/martinkearn/2015/03/25/securing-and-securely-calling-web-api-and-authorize/
My answer to your question will be based on:
I want to ask that other application if a user is valid or have users
ask the other application directly themselves and only allow access to
my app views if the answer is yes. So, in essence, I want to protect
my controllers based on the word of a remote application that contains
an exposed api for login/user validation.
Yes, to my humble opinion, oauth token-based approach is overkill for your need. Sometimes, keeping things simple (and stupid?) is best.
Since you are part of a distributed application, I suppose you can (literally) talk to the team in charge of the "other application/website" where requests that hit your controllers will be coming from.
So, I will skip your questions 1, 2, 3 and 4, which are oriented towards the token-based oauth approach, and suggest a rather different, "simplistic" and faster approach, which is the following:
For clarity purpose, let's call the other application "RemoteApp", and your application "MyApp", or "The other team" and "You", respectively.
-Step 1: You (MyApp) exchange a symmetric secret key with the other team (RemoteApp);
-Step 2: MyApp and RemoteApp agree on the algorithm that will be used to hash data that will be posted to MyApp when a user from RemoteApp requests a page on MyApp. Here, you can, for instance, use MD5 or SHA256 algorithms, which are well documented in MSDN and pretty easy to implement in C#;
Step 3: MyApp tells RemoteApp what its needs to be part of the posted data to validate/authenticate the request.
Here is an example:
Step 1: BSRabEhs54H12Czrq5Mn= (just a random secret key. You can choose/create your own);
Step 2: MD5 (this is the algorithm chosen by the 2 parties)
Step 3: The posted request data could include (at least 3 - 4 parameters or form fields, plus the checksum):
- UserName+RemoteApp fully-qualified domain name + someOther blabla data1 + someOther blabla data2 + checksum
The above information will be concatenated, without space. For instance, let's assume:
UserName = fanthom
RemoteApp fully-qualified domain name = www.remote.com
someOther blabla data1 = myControllerName
someOther blabla data2 = myActionName
The checksum will be generated as follows (function prototype):
generateMD5(string input, string secretKey)
which will be called with the following arguments:
string checkSum = generateMD5("fanthomwww.remote.commyControllerNamemyActionName", "BSRabEhs54H12Czrq5Mn=")
Notice that in the first argument the above 4 parameters have been concatenated, without space, and the second argument is the secret symmetric key.
the above will yield (actual md5 checksum):
checkSum = "ab84234a75430176cd6252d8e5d58137"
Then, RemoteApp will simply have to include the checkSum as an additional parameter or form field.
Finally, upon receiving a request, MyApp will simply have to do the same operation and verify the checkSum. That is, concatenate Request.Form["UserName"]+Request.Form["RemoteApp fully-qualified domain name"]+["someOther blabla data1"]+["someOther blabla data2"],
then use the md5 function with the secret key to verify if the obtained checksum matches the one sent in the request coming from RemoteApp.
If the two match, then the request is authentic. Otherwise, reject the request!
That'all Folks!
I seems you need to implement an OpenID/OAuth2 process.
This way, your apps will be able to utilise single-sign-on (SSO) for all your apps, and all you would have to do is set up your MVC5 app as an OpenID/OAuth2 client.
Take a look into Azure AD B2C which is perfectfor this (I am currently implementing this right now for 3 projects I am working on).
https://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
https://azure.microsoft.com/en-us/services/active-directory-b2c/
https://identityserver.io/
So your app is publicly addressable? I can't tell for sure from your description.
Well you only have these issues if a public client is requesting certain page views from you...
Now here's where i'm confused. If it's an external client accessing different parts of your distributed app, ALL the parts have this problem.
Generally the client authenticates itself at one place (say written by Team A), any other subsequent view requests would need to be checked as well (HTTP being connectionless/stateless), including others done by Team A? So your problem would already be solved (because it would be a problem for everyone, and they would have done something using an auth cookie + checking service, use the same checking service...)?
Which leads me to believe that the view requests are internal and not client facing, in which case... why is auth such a big deal?
If you could clarify your scenario around what requests you need to authenticate...
you are on right track. But instead of you implementing OAUTH and OpenIDConnect user third party which does the heavy lifting. One such tool is IdentityServer
https://identityserver.github.io/Documentation/docsv2/
Now answering your question from IdentityServer point of view
An access token needs to be generated -- true
Getting an access token is not part of the Account controller, it's part of OWIN middleware -- yes, for better design
The access token will be sent along with the requests for my contoller actions
My controller actions, decorated with the [Authorize] attribute, will parse the token and do the right thing -- Yes as a part of response header
Questions:
Q1: Should I generate the token or should the other app - the one with the db and user data? The identity server will generate token that you requested.
Q2: My controllers don't know anything about users. That data is in the other app. What specifically are the controllers parsing for under the hood in order to do the right thing? In essence, what specifically tells them, "yes, this request is OK. Return the view. - usually the token is sent back to the identtyServer to check for validity and to get access_token which will check if the user has access rights. if not [Authorize] attribute will throw error message and return
Q3: I started my project awhile back using a standard MVC5 project template that comes with VS2015 because I figured I'd be handling users/login etc. That turned out not to be the case. Am I going to have to go back and redo this project from scratch because that template doesn't fit this requirement or can I do some surgery on it and continue? For instance, I don't know if that template has all the OWIN stuff I need or maybe has too much extra junk (bloated Account controller, Entity Framework, etc.) to be easily transformed/maintained. - Yes u can delete the extra stuffs
Q4: Is token authorization overkill here? Is there an easier way to keep unauthorized users from accessing my controller actions that makes more sense given the nature of the project? -- It is not an over kill. It is the right thing to do for your scenario

Adding a WIF STS service to existing projects

We have several websites that are set up in the following fashion:
Site1.Web - ASP.NET Web Project (.NET 4.0, WebForms)
Common.Core - Class Library Project (all db interaction)
The web project appears once for each site while the Common.Core project is shared among all sites. We have a login form in the web project that, in order to authenticate, calls into the class library. It would call off a code similar to below:
Common.Core.Authenticate auth = new Common.Core.Authenticate(conStr);
bool validLogin = auth.ValidateUser(userName, password);
if(validLogin)
{
Common.Core.User = auth.GetCurrentUser();
}
The higher ups are pushing for a middle layer service/app tier and want to use a more elegant solution to handle single sign on. Therefore, the decision has been made to use a WIF service to handle the login and validation. Furthermore, we want to try to minimize the code that has to change in each web project, i.e. try to keep as many changes as possible in Common.Core.
I have seen a few samples that show how to add an STS reference to a web project. This would work well in a scenario where the user validation isn't factored into another project like Core.Common. However, in our scenario, how could we handle validation while still going through the common class library?
Ideally, I would like to add an STS reference to the Core.Common class library and replace the direct db logic (auth.ValidateUser above) with a call to an STS service. However, is it even possible to do that? Does the request have to initiate in the web project? If so, is the STS reference required in both places?
Any tutorials or resources which follow the same web project -> class library -> STS service path would be greatly appreciated.
I would also recommend using WIF :-)
In a claims based scenario the authentication process is "reversed". Your app will not call anyone, it will receive the needed information from a trusted source (the STS).
The "STS Reference" is not a library reference. It's a logical connection between your app and the trusted source of security tokens. A token is the artifact your app will use to decide what to do with the user request.
I'd agree with #nzpcmad that it is likely you could entirely remove the calls to you Common.Core library. It might be useful to see what else can you do with it. What does the Common.Core.User object give you?
If it is just properties of a user (e.g. name, e-mail, roles, etc) it is very likely you could just create a new version that simply wraps the IPrincipal supplied byt WIF (a ClaimsPrincipal).
For example (approx. no error handling, pseudo-code):
public User CurrentUser()
{
var user = new User();
var cu = HttpContext.Current.User as IClaimsPrincipal;
user.Name = cu.Name;
user.eMail = (cu.Identity as IClaimsIdentity).Claims.First( c=> c.ClaimType = "eMail" ).Value;
return user;
}
As #nzpcmad says, you can use ADFS or some other STS. Your apps would not care.
One way to achieve this is to FedUtil the ASP.NET project with an instance of ADFS. Essentially, authentication is now "outsourced" and you can simply remove the call to the core library from your app. ADFS is then setup to return whatever attributes the program needs for authorisation as claims. You may need to transform these claims attributes to whatever attributes are passed back to the common core in subsequent calls.
Or you could make the common core "claims aware" in the sense that it now recognizes "claims attributes" as opposed to "common core" attributes. This involves using different .NET classes - no hookup to ADFS is required.
Word of warning - your authentication seems to be all DB related. ADFS cannot authenticate against a DB. It can only authenticate against an instance of AD in the domain that ADFS is installed in (or other AD if trust relationship between AD).
If you want to authenticate against a DB you need a custom STS which is then federated with ADFS. See here: Identity Server.

No email is returned upon a successful OpenID login with DotNetOpenAuth ASP.NET MVC Login sample

I have extended DotNetOpenAuth ASP.NET MVC Login sample while trying to get at least an email from the OpenID provider. No matter which provider I use to log in, I get null for ClaimsResponse. Here is the code.
var request = openid.CreateRequest(Request.Form["OpenID"]);
// Require some additional data
request.AddExtension(new ClaimsRequest
{
Email = DemandLevel.Require,
});
return request.RedirectingResponse.AsActionResult();
And this is the snippet to get the ClaimsResponse:
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var claimsResponse = response.GetExtension<ClaimsResponse>();
// This is null!
break;
}
Before trying this on the MVC sample, I have done the same on WebForms sample and got back the requested email! But this is really hard to get wrong as OpenID related code is virtually the same.
I have noticed that generated OpenID URLs differ in the following: openid.ns.alias3=http://openid.net/srv/ax/1.0&openid.alias3.required=alias1,alias2,alias3,alias4&openid.alias3.mode=fetch_request&openid.alias3.type.alias1=http://axschema.org/contact/email&openid.alias3.count.alias1=1&openid.alias3.type.alias2=http://axschema.org/namePerson&openid.alias3.count.alias2=1&openid.alias3.type.alias3=http://schema.openid.net/contact/email&openid.alias3.count.alias3=1&openid.alias3.type.alias4=http://schema.openid.net/namePerson&openid.alias3.count.alias4=1. Basically, the WebForms version generates an URL with all of that appended while the MVC URL lacks this. The net effect is that email is presented to WebForms while there is none for a MVC app.
One last thing I noticed is that e.g. Google shows as if request comes from localhost for a WebForms app and 127.0.0.1 for a MVC app. Maybe that makes all the difference. Still, I am not sure if I am in control here.
The thing that bothers me is that the code is the same for both projects. Still the URLs generated are different and MVC gets no email.
It sounds like your web forms site has the AXFetchAsSreg behavior activated but your MVC one does not. Activate it for your MVC site and you should be good.

Categories