I'm building a custom ASP.NET Identity 2.0 implementation that uses our own data model, another ORM, other business logic, etc. By default, a user is logged in by setting the ApplicationCookie, after which the AuthorizeAttribute recognizes the cookie and logs the user in. For our own implementation, I want to add more ways to log in. For example:
Impersonation
Password reset token
Google Authenticator (two-factor)
SMS (two-factor)
In all these scenarios the user must be logged in, but what actions the user is allowed to perform depends on the way he logged in. For example: when the user logged in using a 'password reset token', he may change his password but not do anything else. When the user logged in with 'username + password', he may do basically everything, except for the actions that need a higher permission level (where the two-factor methods come in play). In order to do this, I want to build a custom AuthorizeAttribute that checks what login method was used, and then decides whether the user may perform the action or not.
The problem I'm facing is that I can set other cookies than ApplicationCookie (e.g. the TwoFactorCookie that is being set by going through the SMS process), but those cookies are not recognized as authentication cookies. Thus, when I have a TwoFactorCookie, I can't use that cookie to log in. Only having an ApplicationCookie results in a log in.
The issues I'm struggling with:
Do I always need to use ApplicationCookie to log in or can I use custom cookies to log in as well (so for example I can log in using ApplicationCookie, TwoFactorCookie and XYZCookie?
Should I have different cookies for each authentication method or should I have only 1 cookie and store the authentication method/type in a different way (for example in a Claim)?
If I should use different cookies, should I also write custom authentication middleware for each authentication method/type or can I use the default CookieAuthenticationMiddleware? As far as I know, the only thing that has to be done is set a cookie, and flag it with the correct authentication method so I can see how the user was logged in.
Edit:
As per Hao Kung's suggestion I made a couple of extension methods that look like this:
public static void UseSmsSignInCookie(this IAppBuilder app, TimeSpan expires)
{
if (app == null)
throw new ArgumentNullException("app");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = ApplicationAuthenticationTypes.Sms,
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookiePrefix + ApplicationAuthenticationTypes.Sms,
ExpireTimeSpan = expires,
});
}
I try to log someone in by calling AuthenticationManager.SignIn with a custom ClaimsIdentity that has my custom AuthenticationType (SMS). This doesn't work though: after calling SignIn, the result of HttpContext.Current.User.Identity.AuthenticationType still equals ApplicationCookie. The cookie has been set as expected though.
Does anyone have an idea what I'm missing?
So each instance of a CookieMiddleware basically represents one auth cookie, if you want multiple cookies, you can add more than one CookieMiddleware and to retrieve the ClaimsIdentity mapping to your cookie, you just need to call Authenticate on the AuthenticationManager passing in the AuthenticationType for the cookie you want.
Related
To add ADFS 3.0 authentication in our SPA we use the javascript sample and one (wsfed) external identityprovider, and we also add a local api for the SPA client
We also added a custom view to the login process, where the user could "Select WorkingContext" and we could set an additional claim.
Problem: How to add and retrive that additional claim?
Since we simply need the a few of the claim from ws federation, we've made a super simple callback where we just do the following (I'm answering the questions from the docs)
Handling the callback and signing in the user
inspect the identity returned by the external provider.
Yes, we get correct claims from wsfed
make a decision how you want to deal with that user. This might be different based on the fact if this is a new user or a returning user. New users might need additional steps and UI before they are allowed
in.
No additional steps required
probably create a new internal user account that is linked to the external provider.
No, we don't need the user, we just need the a few of the claims from wsfed, so we just return a TestUser based on the wsfed sub in FindUserFromExternalProvider
store the external claims that you want to keep.
Do we need to store the claims, will the claims not be embedded in the jwt token, and the token simply validated?
delete the temporary cookie
ok
sign-in the user
Here we would like to show a custom ui where the user should select a "workingcontext", and we could add the "workingcontext" as an additional claim.
Assuming the above is valid, how can we in step 6 add the extra claim?
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
doesn't seem to give any ways to add claims.
This is how we try to pass the additional claims through the login process:
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
We just need to implement the IProfileService, that's all.
Im doing claims based user role authentication. For this authentication i tested the following:
var identity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.Name, name),
new Claim(ClaimTypes.Email, email),
new Claim(ClaimTypes.StreetAddress, Address),
new Claim(ClaimTypes.Role, "Admin")
},
My authentication works just fine, but the i realized that i should implement some kind of security in order to avoid that the user can tamper the set role.
Therefore i stumbled across this, that is supposed to be added in the Global.asax:
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;
I can't seem to exactly understand what this code above does? Am i right, if i say that it gives the cookie, that the user holds, a unique token made from the email, that then is used to validate the legitimacy of the user by the system?
Im new to this, by the way, so go easy on me :-)
I'm not quite sure what you're meaning by your question, but let me try to clear out a few things.
First, let's talk about your AntiForgeryConfig line of code. What it does is configure the AntiForgeryToken to use the Email claim to identify the request (creates a token based on the email). The AntiForgeryToken allows you to trust a request and prevent Cross-Site Request Forgery (CSRF).
It is implemented in 2 parts. First you need to add the AntiForgeryToken to the form (#Html.AntiForgeryToken). Second, you need to validate the token in your controllers' actions (ValidateAntiForgeryTokenAttribute).
Here is a link to explain what CSRF
Here is a link with up to date code how to implement it
As a side note, you said ... to avoid that the user can tamper the set role. AntiForgeryToken doesn't do anything about tampering roles. Tampering roles would more related to your authentication process.
Is there a way to pass a value back to a relying party after login? e.g. on the querystring?
Background:
What we want to do is inform the relying party what action the user took, e.g. sign in or register, so that the relying party can display the appropriate confirmation message to the user. Because the relying party might link to a Sign Up page, but then instead of signing up the user signs in, so the relying party shouldn't display a "thanks for joining us" notification panel.
I tried adding &lastaction=signup to the returnUrl but that gets lost when the form is posted through Azure ACS.
Next attempt was to try to add lastaction to the wreply, like so:
WSFederationMessage message;
WSFederationMessage.TryCreateFromUri(uri, out message);
var signinMessage = wsFederationMessage as SignInRequestMessage;
if (signinMessage != null)
{
signinMessage.Reply += "?lastaction=hello";
...
In Fiddler I can see that the next POST to ACS posts to https://xxxxx.accesscontrol.windows.net/v2/wsfederation?lastaction=hello
But the lastaction is not passed on to my relying party.
We had a related problem: we wanted to let the RP know which authentication methods the user used when signing in. We solved this by creating a new "system" claim with our namespace, and put the information in there.
In our TokenService implementation, in the AddSecurityClaims method:
claimsIdentity.AddClaim(
new Claim(
String.Format("{0}/{1}", WellKnownConfiguration.TokenService.ClaimsNamespace,
ClaimsAuthenticationMethods), ((int) userAuthenticationMethods)));
Update
You mentioned you thought about using cookies. In that case, I would do the following. I would implement setting a cookie (e.g. when registration page) and then create one more "action" that would return the value of that cookie. When the app gets the POST request with the credentials, you'd perform a redirect (immediately) to that relaying action with a return url. That action would then append the value of the cookie and call the original RP, but a custom action, that would then properly display the view.
Think of it as a cookie proxy. To summarize, the process is as follows:
User hits the RP, action requires authentication
The RP redirects the user to the STS as per WS-Federation
STS issues a token, and also adds a cookie to its own domain
RP gets the authenticated user, redirects to STS Cookie Reader
STS redirects to RP's second screen that can handle the login properly
All in all, one more hop, but like I said, it's probably fast enough for the user to not notice and/or care.
I've set up an STS with WIF and want to expose whether a user is signed in so that an RP can determine if the user is signed in without requiring the user to redirect to the STS and return. If the user is signed in, a different process flow will occur on the RP so it's important to know but not to force the sign-in at this point in the process.
My plan was to create a simple generic handler on the STS which, when hit via an HttpWebRequest, returns the output of context.User.Identity.IsAuthenticated (where context is the HttpContext passed into the ProcessRequest method:
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
if (context.User != null && context.User.Identity.IsAuthenticated)
context.Response.Write("True");
else
context.Response.Write("False");
}
My problem is, if the user is signed in and I hit this handler directly, it returns True but if I programmatically hit the handler from the RP, it returns False (in fact the Identity is null).
Am I just completely wrong in doing this and hitting the handler from the RP will return the status of the User on the RP or could I be doing something wrong?
This handler will only work (return true), if you send the STS authentication cookies with the request. Only your web browser may have these cookies. Therefore it can't be done via HttpWebRequest. Also this is why it works, when you call the handler directly from the browser.
I know this is a bit old thread, but the answer may help others who land on this page.
The thing that does the magic behind the authentication is the session and authentication cookies which are sent to the user's client (e.g. browser) from your STS app. I'm not sure how your STS and RP apps are designed and communicate, so I will keep the answer generic. To notify your RP app of the authentication status, you need to:
1) either somehow share both cookies between the user's client and the your RP app. In this scenario, I'm afraid you will have to build your own client and make your users use it to visit the STS app. This is because you cannot get the cookies from the standard browsers. The client you build sends the cookies somewhere where your RP app can get them and place them in HttpWebRequest.CookieContainer which then can successfully get the result of your handler. I'm only explaining this method to say that it is doable and show how complex and twisted it is.
2) or you will have to track the login status of your users. Instead of checking the context.User, your handler must get the user ID from the calling RP app and then check if that user is logged in (that is there is an active session for that user). For example, you can track or store your sessions in the database, or have a look at the following thread for some methods of accessing active sessions:
List all active ASP.NET Sessions
Is it possible to validate a user's Username + Password without logging them in? I understand a usual login block will look like this:
if (!Page.IsValid)
return;
if (Membership.ValidateUser(UsernameInput.Text, PasswordInput.Text))
{
FormsAuthentication.RedirectFromLoginPage(UsernameInput.Text, false);
}
else { [...] }
With the Membership.ValidateUser() call setting the cookie for the response.
However, there are some additional checks I'd like to perform after the password is confirmed. (Pulling out an expiry date for that user, for example).
Is there a way to do it without just calling FormsAuthentication.SignOut(); after invalidating the page?
I think your assumption that Membership.ValidateUser is setting the cookie is wrong. I don't believe it sets cookies.
FormsAuthentication.RedirectFromLoginPage sets the cookie.
Think about it from a separation of concerns standpoint:
Membership is where you store your users and password, but it really doesn't know anything about the web or cookies.
Forms Authentication is the technology that keeps a user logged into a website but doesn't care how you determine if that user is valid. All it cares about is having a username and setting/reading cookies, so that the user doesn't have to log in with every
request.