Identityserver4 and Redis cache not thread safe? - identityserver4

We use IdentityServer4 to protect our ASPnetCore API's on Azure. This afternoon we were challenged with a very strange occurrence.
One of our API's simply returns all items from a database table based on the sub claim of our user (e.g. userid). Today two users reported seeing not their own items. This code has been running flawless for some years now.
Our startup.cs contains the following:
services.AddStackExchangeRedisCache(action =>
{
action.Configuration = Configuration["RedisConnectionString"];
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["identityServerUrl"];
options.ApiName = "<<redacted>>";
options.ApiSecret = "<<redacted>>";
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(5);
});
Because of timeout issues on Redis, we added the following line:
ThreadPool.SetMinThreads(300, 300);
Could this have anything to do with userid's being swapped?
As it is a protected API, all requests have a bearer token which is validated in Redis with the above logic. How could the userid suddenly be different?
Thanks for thinking with me!

It seems the call of seeing all wrong data was misinterpreted and the logic mentioned above still works as expected. The userid is correct.
We still have some other issue to work out though, but that is not related to this question.

Related

How to re-use token using snowflake node connector?

I have been asked to report an issue with connecting to Snowflake using the node connector here.
Issue: https://github.com/snowflakedb/snowflake-connector-nodejs/issues/113
The issue is I can't find any documentation on how to re-use an existing token to avoid taking a long time when connecting to Snowflake.
Would appreciate any help.
EDIT
Here is the code I use:
// Tokens are retrieved from a DB
if (tokens) {
connection.masterToken = tokens.masterToken;
connection.masterTokenExpirationTime = tokens.masterTokenExpirationTime;
connection.sessionToken = tokens.sessionToken;
connection.sessionTokenExpirationTime = tokens.sessionTokenExpirationTime;
}
connection.connect(async function (err, conn) {
if (err) {
reject(err);
} else {
resolve();
}
});
This might not be a full answer, but hopefully it helps you or someone else. I've had similar issues. For us the process is to get a JWT token via a web service. I haven't tested this, but suspect this could be re-used.The JSON response includes a "lease_duration" property. I'm guessing this is in seconds, but do not know though I tried to check. To give you an idea, one value I got for this is 2764800. You could calculate the do something like:
Long leaseDurationInMs = Long.parseLong(result.get("lease_duration"));
Date estimatedLeaseExpiration = new Date(leaseStartTime+leaseDurationInMs);
System.out.println("Estimated lease expiration timestamp (human readable): "+estimatedLeaseExpiration);
Long estimatedLeaseExpirationInMs = estimatedLeaseExpiration.getTime();
and if then check this value each time you would have fetched whatever this token thing is to see if you need to get another one.
Sorry for answering my own question but I ended up caching the data on my side to avoid connecting too often.

CouchDB permanent authentication key

We're moving and updating our database because it's due for it, but we have an issue concerning authentication. We'd like to connect to the database only with an authentication key.
Our old CouchDB were not using any user and all the databases were public (no users permissions or anything like it). It was working but it is not what we want.
Now, with our 'new' CouchDB, we'd like to have our connections made with an authentication key only, but it looks like there's an expiration on the sessions made and we can't find the way to have a token permanent.
For the context, I'm using couchdb-python for my tools and I found some ways to start a session and get the cookies, therefore the authentication key, but either it is via couchdb-python or the web platform (Fauxton I think it's called), the expiration time is still there and after the timeout (as shown below) the session does expire.
Below is our local.ini for it.
We tried to add both required_valid_user = false and allow_persistent_cookies = true but to no avail.
[couchdb]
uuid = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[couch_peruser]
[chttpd]
port = 5984
bind_address = 192.168.140.66
require_valid_user = false
[httpd]
[couch_httpd_auth]
require_valid_user = false
secret = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
allow_persistent_cookies = true
timeout = 600
[ssl]
[vhosts]
[admins]
admin = -xxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xx
I'm pretty sure there's something we're overlooking or that we did not understand correctly.
Is there a way to get an authentication key to be permanent?
Is there a way to get an authentication key to be permanent?
The best would just be to use password authentication. The password never changes (unless you change it, of course).
But if you insist on using a token, you can increase the session timeout to some insane value:
[couch_httpd_auth]
timeout = 99999999999999
This is untested. I don't know what the maximum value is.

Updating claims with Azure B2C / .NET Core

I've spent some time getting my MVC 6 .NET Core website working with Azure B2C and everything seems to be working great. However, there are a few questions surrounding claims that I can't seem to figure out the correct strategy.
Say a user signs up on my site with email, firstname, lastname. Once the registration is complete, I would like to add a record into a UserProfile table in my database that references this user.
Question 1:
Should I create a "UserProfileId" claim in Azure B2C? Or should I create an "ObjectId" field in my database table that references the AD user? What would make more sense?
Question 2:
Once a user registers, where and how would I update an AD User claim? Would I do it in one of these events? Or somewhere else? I see there is a "User is new" claim that I could check for?
OnAuthenticationValidated
OnAuthorizationCodeReceived
OnRedirectToAuthenticationEndpoint
Question 3:
To update the claims, would I use: Microsoft.Azure.ActiveDirectory.GraphClient? Does anyone have any sample code for how to update a custom claim? I've tried this but it doesn't seem to persist:
var identity = context.AuthenticationTicket.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("EmployeeId", "33"));
Here is my authentication configuration. Thanks!!!!!
public void ConfigureAuth(IApplicationBuilder app, IOptions<PolicySettings> policySettings, AuthenticationHelper authHelper)
{
app.UseCookieAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.AccessDeniedPath = "/Home/Forbidden";
options.CookieSecure = CookieSecureOption.Always;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
app.UseOpenIdConnectAuthentication(options =>
{
options.PostLogoutRedirectUri = policySettings.Value.PostLogoutRedirectUri;
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.ClientId = policySettings.Value.ClientId;
options.CallbackPath = new PathString("/signin-mysite");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.ResponseType = OpenIdConnectResponseTypes.IdToken;
options.Authority = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", policySettings.Value.AadInstance, policySettings.Value.Tenant);
options.Events = new OpenIdConnectEvents {
OnAuthenticationValidated = OnAuthenticationValidated,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
OnAuthenticationFailed = OnAuthenticationFailed,
OnRedirectToAuthenticationEndpoint = OnRedirectToAuthenticationEndpoint
};
options.ConfigurationManager = new PolicyConfigurationManager(
String.Format(CultureInfo.InvariantCulture, "{0}/{1}/{2}/{3}", policySettings.Value.AadInstance, policySettings.Value.Tenant, "v2.0", OpenIdProviderMetadataNames.Discovery),
new string[] { policySettings.Value.SignUpInPolicyId, policySettings.Value.ProfilePolicyId, policySettings.Value.PasswordPolicyId });
});
}
Question 1: Should I create a "UserProfileId" claim in Azure B2C? Or should I create an "ObjectId" field in my database table that references the AD user? What would make more sense?
1a - I didn't add anything to the B2C tenant.
1b - I take the object id from B2C and store it in my table as an alternate key. My table has a unique id of it's own. Should I ever wish to have additional identity providers, this will be necessary.
I only use the object id from B2C to look up users and get my own id.
Question 2: Once a user registers, where and how would I update an AD User claim? Would I do it in one of these events? Or somewhere else? I see there is a "User is new" claim that I could check for?
When you say "update the claim" do you mean update permanently in the B2C tenant, or do you mean add it to the other claims and use it temporarily during the life of this particular token?
There's no connection back to B2C without using the graph client.
The userIsNew claim comes from B2C one time and only at the end of the signup process. You use that to determine if you have a new user trying to access your system. I hook that to create new entries in my tables from the claims that B2C gives me and from then on, the claims all come from the information in my tables.
Question 3: To update the claims, would I use: Microsoft.Azure.ActiveDirectory.GraphClient? Does anyone have any sample code for how to update a custom claim? I've tried this but it doesn't seem to persist:
I have to ask the "update" question again.
What you may be looking for is to "transform" the claims. That's usually done during a TicketReceived event for cookies. That occurs when they have authenticated for the first time. (Not to be confused with signing up.)
I'm not all that bright, but I'll tell you that I spent waaaay too much time on this trying to get it right. Mostly it's because there are a vast number of options and no one can tell you all the right ones for your project. So you just see reams of information that you have go through to find the bits you want.
I found this book (and it's author) incredibly helpful. It's current and he's a Microsoft guy who writes really well.
HTH
Regarding question 1: I did the same as nhwilly: I store the additional information in my database.
Regarding question 2: you can add claims in the OnsigningIn event:
app.UseCookieAuthentication(new Microsoft.AspNetCore.Builder.CookieAuthenticationOptions()
{
Events = new CookieAuthenticationEvents()
{
OnSigningIn = (context) =>
{
ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
identity.AddClaim(new Claim("sb:tID", "555"));
return Task.FromResult(0);
}
}
});
I got the information from Transforming Open Id Connect claims in ASP.Net Core.
Regarding question 3: I haven't done it myself, but this link should get you kickstarted: https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
Hope that helps!

Authentication request returned unexpected result 404

Following is a code to get google contacts.
It was working fine but since few days I m getting exception of "Authentication request returned unexpected result: 404".
using Google.GData.Client;
using Google.Contacts;
using Google.GData.Extensions;
private void FetchContactList()
{
List<string> lstContacts = new List<string>();
RequestSettings rsLoginInfo = new RequestSettings("my application", "abc#gmail.com", "XXXXXX");
rsLoginInfo.AutoPaging = true;
ContactsRequest cRequest = new ContactsRequest(rsLoginInfo);
Feed<contact> feedContacts = cRequest.GetContacts();
foreach (Contact gmailAddresses in feedContacts.Entries)
{
// Looping to read email addresses
foreach (EMail emailId in gmailAddresses.Emails)
{
lstContacts.Add(emailId.Address);
}
}
GridView1.DataSource = lstContacts;
GridView1.DataBind();
}
Is google change something from their side?
Please suggest me way to solve the problem.
Update your current api with google api to version 3 and then make changes to the code according. Probably this may be the reason for the error.
https://developers.google.com/analytics/devguides/reporting/core/v3/gdataLibraries
i further suggest you use oauth2.0 for authentication as per the current requirements of your application and if you are using the older version of the api then you must use oauth2.0
Here is the link for oauth2.0 support: https://developers.google.com/google-apps/spreadsheets/authorize
though this is basically for spreadsheets but you can see how it is and working and change it to the requirements of your gmail api

Building realtime app using Laravel and Latchet websocket

I'm building a closed app (users need to authenticate in order to use it). I'm having trouble in identifying the currently authenticated user from my Latchet session. Since apache does not support long-lived connections, I host Latchet on a separate server instance. This means that my users receive two session_id's. One for each connection. I want to be able to identify the current user for both connections.
My client code is a SPA based on AngularJS. For client WS, I'm using the Autobahn.ws WAMP v1 implementation. The ab framework specifies methods for authentication: http://autobahn.ws/js/reference_wampv1.html#session-authentication, but how exactly do I go about doing this?
Do I save the username and password on the client and retransmit these once login is performed (which by the way is separate from the rest of my SPA)? If so, won't this be a security concearn?
And what will receive the auth request server side? I cannot find any examples of this...
Please help?
P.S. I do not have reputation enough to create the tag "Latchet", so I'm using Ratchet (which Latchet is built on) instead.
Create an angularjs service called AuthenticationService, inject where needed and call it with:
AuthenticationService.check('login_name', 'password');
This code exists in a file called authentication.js. It assumes that autobahn is already included. I did have to edit this code heavily removing all the extra crap I had in it,it may have a syntax error or two, but the idea is there.
angular.module(
'top.authentication',
['top']
)
.factory('AuthenticationService', [ '$rootScope', function($rootScope) {
return {
check: function(aname, apwd) {
console.log("here in the check function");
$rootScope.loginInfo = { channel: aname, secret: apwd };
var wsuri = 'wss://' + '192.168.1.11' + ':9000/';
$rootScope.loginInfo.wsuri = wsuri;
ab.connect(wsuri,
function(session) {
$rootScope.loginInfo.session = session;
console.log("connected to " + wsuri);
onConnect(session);
},
function(code,reason) {
$rootScope.loginInfo.session = null;
if ( code == ab.CONNECTION_UNSUPPORTED) {
console.log(reason);
} else {
console.log('failed');
$rootScope.isLoggedIn = 'false';
}
}
);
function onConnect(sess) {
console.log('onConnect');
var wi = $rootScope.loginInfo;
sess.authreq(wi.channel).then(
function(challenge) {
console.log("onConnect().then()");
var secret = ab.deriveKey(wi.secret,JSON.parse(challenge).authextra);
var signature = sess.authsign(challenge, secret);
sess.auth(signature).then(onAuth, ab.log);
},ab.log
);
}
function onAuth(permission) {
$rootScope.isLoggedIn = 'true';
console.log("authentication complete");
// do whatever you need when you are logged in..
}
}
};
}])
then you need code (as you point out) on the server side. I assume your server side web socket is php coding. I can't help with that, haven't coded in php for over a year. In my case, I use python, I include the autobahn gear, then subclass WampCraServerProtocol, and replace a few of the methods (onSessionOpen, getAuthPermissions, getAuthSecret, onAuthenticated and onClose) As you can envision, these are the 'other side' of the angular code knocking at the door. I don't think autobahn supports php, so, you will have to program the server side of the authentication yourself.
Anyway, my backend works much more like what #oberstat describes. I establish authentication via old school https, create a session cookie, then do an ajax requesting a 'ticket' (which is a temporary name/password which i associate with the web authenticated session). It is a one use name/password and must be used in a few seconds or it disappears. The point being I don't have to keep the user's credentials around, i already have the cookie/session which i can create tickets that can be used. this has a neat side affect as well, my ajax session becomes related to my web socket session, a query on either is attributed to the same session in the backend.
-g
I can give you a couple of hints regarding WAMP-CRA, which is the authentication mechnism this is referring:
WAMP-CRA does not send passwords over the wire. It works by a challenge-response scheme. The client and server have a shared secret. To authenticate a client, the server will send a challenge (something random) that the client needs to sign - using the secret. And only the signature is sent back. The client might store the secret in browser local storage. It's never sent.
In a variant of above, the signing of the challenge the server sends is not directly signed within the client, but the client might let the signature be created from an Ajax request. This is useful when the client was authenticated using other means already (e.g. classical cookie based), and the signing can then be done in the classical web app that was authenticating.
Ok, Greg was kind enough to provide a full example of the client implementation on this, so I wont do anything more on that. It works with just a few tweaks and modifications to almost any use-case I can think of. I will mark his answer as the correct one. But his input only covered the theory of the backend implementation, so I will try to fill in the blanks here for postparity.
I have to point out though, that the solution here is not complete as it does not give me a shared session between my SPA/REST connection and my WS connection.
I discovered that the authentication request transmitted by autobahn is in fact a variant of RPC and for some reason has hardcoded topic names curiously resembling regular url's:
- 'http://api.wamp.ws/procedure#authreq' - for auth requests
- 'http://api.wamp.ws/procedure#auth' - for signed auth client responses
I needed to create two more routes in my Laravel routes.php
// WS CRA routes
Latchet::topic('http://api.wamp.ws/procedure#authreq', 'app\\socket\\AuthReqController');
Latchet::topic('http://api.wamp.ws/procedure#auth', 'app\\socket\\AuthReqController');
Now a Latchet controller has 4 methods: subscribe, publish, call and unsubscribe. Since both the authreq and the auth calls made by autobahn are RPC calls, they are handled by the call method on the controller.
The solution first proposed by oberstet and then backed up by Greg, describes a temporary auth key and secret being generated upon request and held temporarily just long enough to be validated by the WS CRA procedure. I've therefore created a REST endpoint which generates a persisted key value pair. The endpoint is not included here, as I am sure that this is trivial.
class AuthReqController extends BaseTopic {
public function subscribe ($connection, $topic) { }
public function publish ($connection, $topic, $message, array $exclude, array $eligible) { }
public function unsubscribe ($connection, $topic) { }
public function call ($connection, $id, $topic, array $params) {
switch ($topic) {
case 'http://api.wamp.ws/procedure#authreq':
return $this->getAuthenticationRequest($connection, $id, $topic, $params);
case 'http://api.wamp.ws/procedure#auth':
return $this->processAuthSignature($connection, $id, $topic, $params);
}
}
/**
* Process the authentication request
*/
private function getAuthenticationRequest ($connection, $id, $topic, $params) {
$auth_key = $params[0]; // A generated temporary auth key
$tmpUser = $this->getTempUser($auth_key); // Get the key value pair as persisted from the temporary store.
if ($tmpUser) {
$info = [
'authkey' => $tmpUser->username,
'secret' => $tmpUser->secret,
'timestamp' => time()
];
$connection->callResult($id, $info);
} else {
$connection->callError($id, $topic, array('User not found'));
}
return true;
}
/**
* Process the final step in the authentication
*/
private function processAuthSignature ($connection, $id, $topic, $params) {
// This should do something smart to validate this response.
// The session should be ours right now. So store the Auth::user()
$connection->user = Auth::user(); // A null object is stored.
$connection->callResult($id, array('msg' => 'connected'));
}
private function getTempUser($auth_key) {
return TempAuth::findOrFail($auth_key);
}
}
Now somewhere in here I've gone wrong. Cause if I were supposed to inherit the ajax session my app holds, I would be able to call Auth::user() from any of my other WS Latchet based controllers and automatically be presented with the currently logged in user. But this is not the case. So if somebody see what I'm doing wrong, give me a shout. Please!
Since I'm unable to get the shared session, I'm currently cheating by transmitting the real username as a RPC call instead of performing a full CRA.

Resources