Here is how I've created an claim based authorization attribute. But I have some doubts about how this work.
Given the code from my startup class:
public void Configuration(IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authentication:Authority"],
RequiredScopes = ConfigurationManager.AppSettings["Authentication:Scopes"].Split(' ').ToList(),
PreserveAccessToken = true
});
}
I was expecting that if I have this attribute to my controller and I send an invalid token(invalid signature) the request will be automatically rejected as unauthorized, but the code from the attribute is executed.
Shouldn't OWIN validate the token first?
How to make sure that the token is valid (valid stricture, signature, not expired, etc) and only after validate the claims?
The issue is within your linked question in your ClaimAuthorizationAttribute - it doesn't ever call base.IsAuthorized(), thus bypassing the built in protection mechanisms offered by AuthorizeAttribute.
Instead of just returning here after seeing whether or not the claim is present:
return token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
You should instead carry on with making sure that the base class is satisfied, and thus the token itself is valid, too:
var claimValid = token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
if (claimValid)
return base.IsAuthorized();
else
return false;
Related
I am trying to verify the email confirmation token for user but always getting INVALID TOKEN error no matter what I do.
My code is very simple
To Generate the token
EmailVerificationCode = await userManager.GenerateEmailConfirmationTokenAsync(user);
EmailVerificationHTMLFormatCode = HttpUtility.UrlEncode(EmailVerificationCode);
To Verify the Token
var result = await userManager.ConfirmEmailAsync(user, code);
I always get INVALID TOKEN error.
The things I have tried
Checking both generated token and received token for verification by putting them into the database, they are exactly the same.
Tried using HttpUtility.UrlDecode(Code) to decode the token at receiving end
Tried to just use RAW token without HttpUtility.UrlEncode to verify it
I also went through the following solutions
Asp.net 2.0 Identity, ConfirmEmailAsync() getting Invalid Token
Invalid Token. while verifying email verification code using UserManager.ConfirmEmailAsync(user.Id, code)
AspNet.Identit 2.1.0 ConfirmEmailAsync always returns Invalid token
Asp.NET Identity 2 giving "Invalid Token" error
No matter what I do, it is always an Invalid Token where I can clearly see the token is 100% correct.
Any idea what am I doing wrong?
Edit - If this helps, my Startup.cs has the following Identity configurations
// For Identity
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
There are a few questions about this implementation before I answer.
Do you implement a custom DataProtectionProvider?
Do you override the default token life time is x number of hours?
What you can do over come this issue, create a customer DataProtectionProvider like below,
public class EmailConfirmationTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
{
public EmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
}
Then inject in configure services with lifespan is 2 hours.
services.AddTokenProvider<EmailConfirmationTokenProvider<User>>("emailconfirmation");
services.Configure(opt =>
opt.TokenLifespan = TimeSpan.FromHours(3));
Thank you
I am working with two identity providers, both implemented using IdentityServer4 in ASP.NET MVC Core 2.2. One of them is used as an external provider by the other. Let's call them "primary" and "external". The primary provider is referenced directly by the web application. The external provider is an optional login method provided by the primary provider.
The web application uses the oidc-client-js library to implement authentication. The logout operation in the web app calls UserManager.signoutRedirect. This works fine when the primary identity provider is used (no logout confirmation prompt is shown). However, when the external provider is used, the user is prompted to sign out from the external provider.
The sequence of requests when logging out are:
GET http://{primary}/connect/endsession?id_token_hint=...&post_logout_redirect_uri=http://{webapp}
GET http://{primary}/Account/Logout?logoutId=...
GET http://{external}/connect/endsession?state=...&post_logout_redirect_uri=http://{primary}/signout-callback-{idp}&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.3.0.0
GET http://{external}/Account/Logout?logoutId=...
This last request above shows the logout confirmation screen from the external provider.
The code for the /Account/Logout page on the primary provider is almost identical to the sample code in the documentation:
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
var vm = await BuildLogoutViewModelAsync(logoutId);
if (!vm.ShowLogoutPrompt)
{
// If the request is authenticated don't show the prompt,
// just log the user out by calling the POST handler directly.
return Logout(vm);
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated)
{
// delete local authentication cookie
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
var ap = new AuthenticationProperties { RedirectUri = url };
return SignOut(ap, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
The BuildLogoutViewModelAsync method calls GetLogoutContextAsync to check if the logout is authenticated, like so:
public async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
{
var vm = new LogoutViewModel
{
LogoutId = logoutId,
ShowLogoutPrompt = true
};
var context = await _interaction.GetLogoutContextAsync(logoutId);
if (context?.ShowSignoutPrompt == false)
{
// It's safe to automatically sign-out
vm.ShowLogoutPrompt = false;
}
return vm;
}
The BuildLoggedOutViewModelAsync method basically just checks for an external identity provider and sets the TriggerExternalSignout property if one was used.
I hate to make this a wall of code, but I'll include the ConfigureServices code used to configure the primary identity server because it is probably relevant:
var authenticationBuilder = services.AddAuthentication();
authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions);
void ConfigureOptions(OpenIdConnectOptions opts)
{
opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
opts.SignOutScheme = IdentityServerConstants.SignoutScheme;
opts.Authority = openIdConfig.ProviderAuthority;
opts.ClientId = openIdConfig.ClientId;
opts.ClientSecret = openIdConfig.ClientSecret;
opts.ResponseType = "code id_token";
opts.RequireHttpsMetadata = false;
opts.CallbackPath = $"/signin-{openIdConfig.Scheme}";
opts.SignedOutCallbackPath = $"/signout-callback-{openIdConfig.Scheme}";
opts.RemoteSignOutPath = $"/signout-{openIdConfig.Scheme}";
opts.Scope.Clear();
opts.Scope.Add("openid");
opts.Scope.Add("profile");
opts.Scope.Add("email");
opts.Scope.Add("phone");
opts.Scope.Add("roles");
opts.SaveTokens = true;
opts.GetClaimsFromUserInfoEndpoint = true;
var mapAdditionalClaims = new[] { JwtClaimTypes.Role, ... };
foreach (string additionalClaim in mapAdditionalClaims)
{
opts.ClaimActions.MapJsonKey(additionalClaim, additionalClaim);
}
opts.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
}
My understanding is that the id_token_hint parameter passed to the first /connect/endsession endpoint will "authenticate" the logout request, which allows us to bypass the prompt based on the ShowSignoutPrompt property returned by GetLogoutContextAsync. However, this does not happen when the user is redirected to the external provider. The call to SignOut generates the second /connect/endsession URL with a state parameter, but no id_token_hint.
The logout code in the external provider is basically the same as the code shown above. When it calls GetLogoutContextAsync, that method does not see the request as authenticated, so the ShowSignoutPrompt property is true.
Any idea how to authenticate the request to the external provider?
The final block of code, you hate, but luckily added, contains one significant row:
opts.SaveTokens = true;
That allows you later to restore the id_token you got from the external provider.Then you can use it as a "second level hint".
if (vm.TriggerExternalSignout)
{
var url = Url.Action("Logout", new { logoutId = vm.LogoutId });
var props = new AuthenticationProperties {RedirectUri = url};
props.SetParameter("id_token_hint", HttpContext.GetTokenAsync("id_token"));
return SignOut(props, vm.ExternalAuthenticationScheme);
}
I had the exact same problem as OP and was able to correct it by explicitly stating that the ID Token is to be added on to the logout request as per this Github Issue
https://github.com/IdentityServer/IdentityServer4/issues/3510
options.SaveTokens = true; // required for single sign out
options.Events = new OpenIdConnectEvents // required for single sign out
{
OnRedirectToIdentityProviderForSignOut = async (context) => context.ProtocolMessage.IdTokenHint = await context.HttpContext.GetTokenAsync("id_token")
};
I have come up with a solution, though it seems to contradict what is done in the samples.
The problem seems to be caused by two lines of code that were both from the IdentityServer samples that we used as a basis for our IDP implementations. The problem code is in the "primary" IDP.
The first line is in ConfigureServices in Startup.cs:
var authenticationBuilder = services.AddAuthentication();
authenticationBuilder.AddOpenIdConnect(openIdConfig.Scheme, "external", ConfigureOptions);
void ConfigureOptions(OpenIdConnectOptions opts)
{
opts.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
opts.SignOutScheme = IdentityServerConstants.SignoutScheme; // this is a problem
The second place is in ExternalController.cs, in the Callback method. Here we diverged from the samples, using IdentityServerConstants.ExternalCookieAuthenticationScheme instead of IdentityConstants.ExternalScheme:
// Read external identity from the temporary cookie
var result = await this.HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// ...
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme); // this is a problem
What happens at logout is: since the SignOutScheme is overridden, it is looking for a cookie that doesn't exist. Simply removing that doesn't fix it because the call to SignOutAsync has deleted the cookie that contains the information required for the identity code to authenticate the scheme. Since it can't authenticate the scheme, it does not include the id_token_hint in the request to the "external" IDP.
I've been able to fix this by removing the code that overrides SignOutScheme in Startup.cs, and moving the code that deletes the ExternalCookieAuthenticationScheme cookie to the Logout endpoint in AccountController.cs:
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// delete temporary cookie used during external authentication
await this.HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// build a return URL so the upstream provider will redirect back...
This way the "temporary" external cookie is left around until it is needed, but is deleted when the user logs out.
I'm not sure if this is the "correct" solution, but it does seem to work correctly in all cases that I've tested. I'm not really sure why we deviated from the sample in ExternalController.cs, either, but I suspect it is because we have two standalone IDP rather than a site with a single standalone IDP. Also, the sample appears to be using implicit flow while we are using hybrid flow.
I have a question about verifying an Auth Token. Currently, I'm generating my Auth Token at /token and sending it back to the client side where I store it in session storage. To make the process as secure as possible I'm looking to store the token in a HttpOnly and Secure cookie to protect against XSS.
I'm sending the cookie along with a XSRF token where on my client side (Angular) I set the X-XSRF-TOKEN header to the XSRF token value. The browser then automatically send the Auth Cookie back to me where I can read the token.
My main question is how do I extract the token and validate it? Before I was using Authentication Bear auth_token with the [Authorize] attribute but that doesn't work now since the token is in a cookie. I made my own Attribute (see below, but I'm stuck on the line where I actually verify the token. I'm not sure what function to call and pass the token to, to validate it.
public class ValidateToken : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
CookieHeaderValue authToken = headers.GetCookies().FirstOrDefault();
CookieState authCookie = authToken.Cookies.Where(p => p.Name == "AUTH-TOKEN").FirstOrDefault();
if (authCookie.Value is valid -> need help here) {
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Edit: I've tried the following
actionContext.Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authCookie.Value);
bool isAuth = base.IsAuthorized(actionContext);
But the RequestContext is already set once OnAuthorization is called and it doesn't update to use the new Header I added.
Is it possible (and if so, how?) to configure a self hosted owin endpoint to use client certificate mapping authentication with A/D? IIS has this feature link, but so far I have not found an equivalent for self-hosted endpoints.
The way in which I have gotten this to work though (bearing in mind this approach is probably not 100% foolproof), is a 2-step process by using a combination of authenticationSchemeSelectorDelegate and OWIN.
This will choose the appropriate AuthenticationScheme (allowing requests containing a cert through, otherwise deferring to NTLM authentication)
public void Configuration(IAppBuilder appBuilder)
{
var listener = (HttpListener)appBuilder.Properties[typeof(HttpListener).FullName];
listener.AuthenticationSchemeSelectorDelegate += AuthenticationSchemeSelectorDelegate;
}
private AuthenticationSchemes AuthenticationSchemeSelectorDelegate(HttpListenerRequest httpRequest)
{
if (!httpRequest.IsSecureConnection) return AuthenticationSchemes.Ntlm;
var clientCert = httpRequest.GetClientCertificate();
if (clientCert == null) return AuthenticationSchemes.Ntlm;
else return AuthenticationSchemes.Anonymous;
}
This will read the contents of the cert and populate the "server.User" environment variable accordingly
public class CertificateAuthenticator
{
readonly Func<IDictionary<string, object>, Task> _appFunc;
public CertificateAuthenticator(Func<IDictionary<string, object>, Task> appFunc)
{
_appFunc = appFunc;
}
public Task Invoke(IDictionary<string, object> environment)
{
// Are we authenticated already (NTLM)
var user = environment["server.User"] as IPrincipal;
if (user != null && user.Identity.IsAuthenticated) return _appFunc.Invoke(environment);
var context = environment["System.Net.HttpListenerContext"] as HttpListenerContext;
if (context == null) return _appFunc.Invoke(environment);
var clientCertificate = context.Request.GetClientCertificate();
// Parse out username from certificate
var identity = new GenericPrincipal
(
new GenericIdentity(username), new string[0]
);
environment["server.User"] = identity;
}
}
Is there not a better/standardized way?
I have not seen any standard components built for this yet. That said, it should be possible to clean up your code a little:
You don't need to downcast to HttpListenerContext to get the client cert. The client cert should already be available in the OWIN environment under "ssl.ClientCertificate". See https://katanaproject.codeplex.com/wikipage?title=OWIN%20Keys. You'll also want to check ssl.ClientCertificateErrors because the cert may not have passed all validation checks.
You don't need the AuthenticationSchemeSelectorDelegate code. You could just set the listner.AuthenticationSchemes = NTLM | Anonymous. Then you add a middleware after your cert middleware that returns a 401 if server.User is not valid.
How might I add a secure token to access the api so that not everyone can get it. I would like the format of my url to be : api.example.com/*key*/person?id=5 and when I send this request it will return if the key is valid if not valid it will return invalid login. I am using mvc 4 api and C# to make this and a link or something will be great.
The key phrase you are looking for most like is that you need to create and add a custom ActionFilterAttribute.
A quick search on google turned up this blog article which talks about doing this exact thing (along with some other filters).
Just in case there's some link rot here's the gist (excerpts from the blog article):
Come up with some scheme for generating/verifying the API tokens
Create you attribute that uses the verification from step 1 in an attribute
Add the attribute to the global configuration
CODE
public class TokenValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
string token;
try
{
token = actionContext.Request.Headers.GetValues("Authorization-Token").First();
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
//This part is where you verify the incoming token
AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
}
}
To make these action filters global, the following code in the Global.asax Application_Start() will do the trick:
var config = GlobalConfiguration.Configuration;
config.Filters.Add(new TokenValidationAttribute());
At my work, we create a hash of the username and password and use that for the user token. You could just generate a GUID for them, keeping track of the time it was created and who it belongs to.