Secure WebApi concept - c#

How about this system. I need some comments and maybe critical security part for this.
System which I use is maybe little bit complicated but 100% custom and should be good. This is a system for custom authentication in sending request to Asp.NET
WebApi
System works with sending 2 request
Everything what you need is 2 pairs of data. 1st one is public and 2nd one is secret.
Second pair of data be must be known to both sides (sender and receiver)
public: ApiKey and RequstID where ApiKey is "normal" and requstID have to be unique always;
secret: UserName and Password (both side have to know these data)
Sender:
Send 1st request with 3 parameters: 1st= ApiKey, 2nd=RequstID, 3rd=Hash(ApiKey+RequestID+USerName+Pass)
Server:
Read RequstID
Read ApiKey and get data about users UserName and Pass for this ApiKey
From the own side: Hash(ApiKey+RequestID+USerName+Pass)
Check is Hash from Sender same us from Server
If is False:
BadRequest - or whatever...
if is True
Before all - Create on database on table for collect data about request.
This is table with columns (e.g.):
ID(autoincrement), RequstID, Token, TokenValidateDateTime
Before create new row, check is there already this RequestID and if there is return BadRequest.
If there is not - make new row.
RequstID is RequstID from request;
Token - Generate token (e.g. Guid.NewGuid().ToString());
TokenValidateDateTime= DateTime.Now.AddMinutes(2) - or some other value ...
In response for the first request send back this this Token (from item 2)
In the second request, Sender have to use AGAIN same RequstID and Token (from response before)
Server will check
Combination RequstID and Token
Token validation (depend on current date time);
Is everything is OK, user is validated
if is not - BadRequest, or whatever
Any suggestions or comments are welcome :)

It sounds similar to traditional website username / password authentication which returns a session cookie.
But you're also including a api key & request id and a hash. The hash won't add to much value unless there's a shared salt, as once someone works out your hashing technique it will be vulnerable to dictionary attacks.
Also generating a Guid token isn't "cryptographically secure", it is designed to be unique but it's often based upon the system clock meaning it is predictable.
Building bespoke security mechanisms are generally unadvised; doing bespoke encryption is defiantly a "no no" (which you aren't doing as far as I'm aware). Bespoke authentication is probably less risky, but seeing as there are many frameworks already existing that have been critiqued by security experts I'd suggest researching if any of those suit first.
I'd recommend looking at asp.net core's security options: https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-2.1

Related

How to invalidate tokens after password change

I am working on an API that uses JWT token auth. I've created some logic behind it to change user password with a verification code & such.
Everything works, passwords get changed. But here's the catch:
Even if the user password has changed and i get a new JWT token when authenticating...the old token still works.
Any tip on how i could refresh/invalidate tokens after a password change?
EDIT: I've got an idea on how to do it since i've heard you can't actually invalidate JWT tokens.
My idea would be to create a new user column which has something like "accessCode" and store that access code in the token. Whenever i change the password i also change accessCode (something like 6 digit random number) and i implement a check for that accessCode when doing API calls (if the accesscode used in the token doesnt match the one in the db -> return unauthorized).
Do you guys think that would be a good approach or is there some other way ?
The easiest way to revoke/invalidate is probably just to remove the token on the client and pray nobody will hijack it and abuse it.
Your approach with "accessCode" column would work but I would be worried about the performance.
The other and probably the better way would be to black-list tokens in some database. I think Redis would be the best for this as it supports timeouts via EXPIRE so you can just set it to the same value as you have in your JWT token. And when the token expires it will automatically remove.
You will need fast response time for this as you will have to check if the token is still valid (not in the black-list or different accessCode) on each request that requires authorization and that means calling your database with invalidated tokens on each request.
Refresh tokens are not the solution
Some people recommend using long-lived refresh tokens and short-lived access tokens. You can set access token to let's say expire in 10 minutes and when the password change, the token will still be valid for 10 minutes but then it will expire and you will have to use the refresh token to acquire the new access token. Personally, I'm a bit skeptical about this because refresh token can be hijacked as well: http://appetere.com/post/how-to-renew-access-tokens and then you will need a way to invalidate them as well so, in the end, you can't avoid storing them somewhere.
ASP.NET Core implementation using StackExchange.Redis
You're using ASP.NET Core so you will need to find a way how to add custom JWT validation logic to check if the token was invalidated or not. This can be done by extending default JwtSecurityTokenHandler and you should be able to call Redis from there.
In ConfigureServices add:
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect("yourConnectionString"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.SecurityTokenValidators.Clear();
// or just pass connection multiplexer directly, it's a singleton anyway...
opt.SecurityTokenValidators.Add(new RevokableJwtSecurityTokenHandler(services.BuildServiceProvider()));
});
Create your own exception:
public class SecurityTokenRevokedException : SecurityTokenException
{
public SecurityTokenRevokedException()
{
}
public SecurityTokenRevokedException(string message) : base(message)
{
}
public SecurityTokenRevokedException(string message, Exception innerException) : base(message, innerException)
{
}
}
Extend the default handler:
public class RevokableJwtSecurityTokenHandler : JwtSecurityTokenHandler
{
private readonly IConnectionMultiplexer _redis;
public RevokableJwtSecurityTokenHandler(IServiceProvider serviceProvider)
{
_redis = serviceProvider.GetRequiredService<IConnectionMultiplexer>();
}
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
// make sure everything is valid first to avoid unnecessary calls to DB
// if it's not valid base.ValidateToken will throw an exception, we don't need to handle it because it's handled here: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
// we have to throw our own exception if the token is revoked, it will cause validation to fail
var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
var claim = claimsPrincipal.FindFirst(JwtRegisteredClaimNames.Jti);
if (claim != null && claim.ValueType == ClaimValueTypes.String)
{
var db = _redis.GetDatabase();
if (db.KeyExists(claim.Value)) // it's blacklisted! throw the exception
{
// there's a bunch of built-in token validation codes: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7692d12e49a947f68a44cd3abc040d0c241376e6/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
// but none of them is suitable for this
throw LogHelper.LogExceptionMessage(new SecurityTokenRevokedException(LogHelper.FormatInvariant("The token has been revoked, securitytoken: '{0}'.", validatedToken)));
}
}
return claimsPrincipal;
}
}
Then on your password change or whatever set the key with jti of the token to invalidate it.
Limitation!: all methods in JwtSecurityTokenHandler are synchronous, this is bad if you want to have some IO-bound calls and ideally, you would use await db.KeyExistsAsync(claim.Value) there. The issue for this is tracked here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/468 unfortunately no updates for this since 2016 :(
It's funny because the function where token is validated is async: https://github.com/aspnet/Security/blob/beaa2b443d46ef8adaf5c2a89eb475e1893037c2/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs#L107-L128
A temporary workaround would be to extend JwtBearerHandler and replace the implementation of HandleAuthenticateAsync with override without calling the base so it would call your async version of validate. And then use this logic to add it.
The most recommended and actively maintained Redis clients for C#:
StackExchange.Redis (also used on stackoverflow) (Using StackExchange.Redis in a ASP.NET Core Controller)
ServiceStack.Redis (commercial with limits)
Might help you to choose one: Difference between StackExchange.Redis and ServiceStack.Redis
StackExchange.Redis has no limitations and is under the MIT license.
So I would go with the StackExchange's one
The simplest way would be: Signing the JWT with the users current password hash which guarantees single-usage of every issued token. This is because the password hash always changes after successful password-reset.
There is no way the same token can pass verification twice. The signature check would always fail. The JWT's we issue become single-use tokens.
Source- https://www.jbspeakr.cc/howto-single-use-jwt/
The following approach brings together the best of each approach proposed previously:
Create the column "password_id" in the "user" table.
Assign a new UUID to "password_id" when creating a user.
Assign a new UUID to "password_id" every time the user changes his password.
Sign the authorization JWTs using the "password_id" of the respective user.
If more performance is needed, simply store the "password_id" of the users in Redis.
Advantages of this approach:
If a user changes his password all JWTs existing up to that moment will automatically become invalid forever.
It does not matter if a user changes his password to an old one.
It is not necessary to store the JWTs in the server side.
It is not necessary to add any extra data in the JWT payload.
The implementation using Redis is very simple.

AuthenticationHeaderValue can not be set

I can trying to set an authentication value just for testing purposes.
I am not using basic authentication but just a String
VC.Request.Headers.Authorization = new AuthenticationHeaderValue("Secret Password");
It gives me this error that is making me pulling my hair off:
The format of value 'Secret Password' is invalid.
Again I don't want to use basic authentication and I don't know whats wrong, help?
The class is "documented" as:
Represents authentication information in Authorization, ProxyAuthorization, WWW-Authneticate[sic], and Proxy-Authenticate header values.
By calling the constructor with one parameter, you're using "Secret Password" as scheme, which can only contain tokens (i.e. no spaces). See RFC 2617 for specification.
You might want to call the other constructor overload:
new AuthenticationHeaderValue("MySuperAuthScheme", "Secret Password");
I think basic authentication generally uses a username:password syntax, so the client-side code might be pre-validating it to stop you sending "bad" data mistakenly to the server, even though that's what you're intentionally trying to do. Try adding a : and see if that helps.

DotNetOpenAuth: Want to access username while storing Nonce

I am using DotNetOpenAuth 4.0.20926 and trying to implement an OAuth2 based Authentication server with Db Nonce provider.
For some purpose I want to access username in NonceStore's StoreNonce function while processing the GetToken request.
I am not getting a way to retrieve Username in that call.
How can I solve this problem?
Hey Andrew thanks for your reply and DotNetOpenAuth.
My GetToken Method is like this
public ActionResult Token()
{
string userName = "";
//Want to fetch username here
//Using username here
var result = this.authorizationServer.HandleTokenRequest(this.Request);
return result.AsActionResult();
}
And I want to fetch the username before calling HandleTokenRequest.
Is there any Message Parser or Helper method to fetch the username from the request data / Code value .
As you've observed, the interface does not pass the username into the StoreNonce method. So the only way you may possibly be able to get the username would be for you to discover what it is first, before you instantiate your INonceStore instance, and pass it to that nonce store first, so that later when StoreNonce is invoked, it already knows the username.
That said, I believe any design where storing and checking a nonce requires the username needs some rethinking. Not only is it a mixing of concerns that otherwise should remain separate, you may be limiting yourself going forward or even introducing security holes.

Is validating a Realm sufficient security?

I'm working on an OpenId Provider for an SSO setup - it's basically a web application portal that shares credentials with any of the "applications" the user has access to. I have the Provider set up and everything is working fine, but I have a question about security.
I want to do some permissions checking on the Provider before it sends a positive assertion to the RP; namely that the user actually has permissions to the application which is making the request.
Here's the Provider code I've got at the moment (just a snippet, can add more if necessary):
private bool AutoRespondIfPossible(out ActionResult response)
{
if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success
&& User.Identity.IsAuthenticated && this.RealmIsValid(ProviderEndpoint.PendingAuthenticationRequest.Realm)) {
if (ProviderEndpoint.PendingAuthenticationRequest != null) {
if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity
|| this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest)) {
ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
response = this.SendAssertion();
return true;
}
}
//we don't want anon requests
if (ProviderEndpoint.PendingAnonymousRequest != null) {
ProviderEndpoint.PendingAnonymousRequest.IsApproved = false;
response = this.SendAssertion();
return true;
}
}
response = null;
return false;
}
Basically what I'm doing is validating that the realm of the request (in the RealmIsValid method) matches to a hostname in my list of acceptable hostnames, and then I'm comparing the user permissions based on the hostname.
What I'm wondering is: How accurate is ProviderEndpoint.PendingAuthenticationRequest.Realm? If I understand correctly, the realm is set by the relying party - is it possible that the endpoint could receive a request from a URI other than the realm specified in that request? Or am I safe to assume that the realm will always be accurate (that is: match the URI of the relying party)?
Yes, the OpenID realm is reliable, due to two steps OpenID 2.0 and DotNetOpenAuth takes:
The OpenID return_to URL must be a derivative of the realm URL. So while anyone can formulate an OpenID request as if it came from any relying party, the alleged relying party will always be the one to actually receive the response, so an attacker operating another RP will not get the response.
Some "open redirector" attacks might allow the attacker to use a return_to URI that is based on a legitimate Realm URL, but happens to be a URL that will redirect to the attacker's web site, thus delivering the assertion to the attacker. This is mitigated by "RP Discovery" which your code snippet includes with its call to the IsReturnUrlDiscoverable method. The RP should explicitly list the allowed return_to URLs in its RP Discovery XRDS document, so that open redirector endpoints are not allowed.
That all said, OpenID is mostly about identifying the user -- not authorizing them to specific RPs. So while what you're doing may be fine, it's a bit off the beaten track for OpenID use, so please consider the security implications carefully (as it sounds like you're doing now).

Can't set FormsAuthenicationTicket.UserData in cookieless mode

I'm trying to implement the "Writing Information to UserData" section of this article, but it doesn't work properly when the cookie is part of the URI.
My code:
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie( userName, createPersistantCookie );
// Get the FormsAuthenticationTicket out of the encrypted cookie
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( authCookie.Value );
// Create a new FormsAuthenticationTicket that includes our custom User Data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket( ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, "foo");
// Update the authCookie's Value to use the encrypted version of newTicket
authCookie.Value = FormsAuthentication.Encrypt( newTicket );
// Manually add the authCookie to the Cookies collection
HttpContext.Current.Response.Cookies.Add( authCookie );
// Determine redirect URL and send user there
string redirUrl = FormsAuthentication.GetRedirectUrl( userName, createPersistantCookie );
HttpContext.Current.Response.Redirect( redirUrl, false );
When cookieless is used, the page redirects but doesn't get the correct URI with the cookie information in it, so it loops back to my Login page where Request.IsAuthenticated returns false. An endless loop ensues.
How do I redirect to the proper URI?
I found this to be an interesting problem, so I set about doing some digging, testing, and a little bit of debugging into the .net framework source.
Basically, what you are trying to do will not work. Anything you put into the Response.Cookies collection will just be ignored if the browser doesn't support cookies. You can check Request.Browser.Cookies to see if cookies are supported.
In asp.net, both session state and authentication support a cookieless mode, but this does not extend to other cookies. In fact, it seems that session and authentication can be set to different modes of operation themselves even.
The authentication system can store it's own data in the URI, but it does so by directly manipulating the URI itself. Sadly, Microsoft doesn't appear to have exposed these capabilities to code outside the authentication module.
Basically, if you use the methods like FormsAuthentication.GetAuthCookie() and FormsAuthentication.SetAuthCookie() then the authentication system will take care of putting that information into the URI for you automagically... but it doesn't allow you to supply a customized authentication ticket to these methods... so you are stuck with the default auth ticket.In these cases, you are on your own for storing any custom data.
Anyway...
There really isn't much advantage to storing custom data directly in an authentication ticket if the authentication system has gone cookieless... in cookieless mode, things like "persistant cookie" have no meaning so you'll be regenerating the data at least once per session anyway.
The most common suggestion for cases where you are cookieless but still need custom data like this is to enable cookieless sessions, and just store your custom data as a session variable. The session ID will get put into the URI, but the custom data will stay in memory on the server. The usage pattern is identical no matter if your sessions are cookieless or not.
If you really wanted to, you could come up with a system of storing the custom data in the URI manually. The easiest thing to do would be to put the custom data into query strings or use pathdata. I can't see any real advantage to this over sessions variables unless you are just deperate not to use server memory (adding a little memory to a server is cheap, ugly URLs and manually writing code to deal with them is not cheap).
Thank you for the great explanation, Stephen. In cases where the user does not allow cookies, I'm just going to have to avoid the UserData and load the data from the database.
Before the code listed above I'll do:
if( !HttpContext.Current.Request.Browser.Cookies || !FormsAuthentication.CookiesSupported )
{
FormsAuthentication.RedirectFromLoginPage( userName, false);
return;
}

Categories