Sessions, auth-tokens and so on - How? Best practices? Recommendations?

Hello,

I’m starting the design of a new API for my software to expose several functions/methods to external clients.

In previous projects I have used a “client side” way to handling the Session in the Server that depends on the client setting the correct ClientId in the messages, before making request to the server. That way, when the method is called, the Session object already has the correct session instance and I can just access it and do whatever I need there.

I was thinking for this new project in using something different: the Login method would return a token (probably a JWT one) that subsequent calls would have to send as parameter. Part of that token would be the SessionId that I would use Server Side to locate and access what could be needed.

But as far as I can see, the Session property for the TRORemoteDataModule class is read only, so I can’t assign directly to it. ClientID is writeable, so, I just need to assign the ClientID and then interntally the Session will be updated with the correct one? Or I have to go and find/use directly the correct session from the SessionManager and no the Session property of the datamodule?

What are the best practices for this kind of uses? The client app will be, to begin at least, an HTTP web app… probably using TCP transport. I don’t remember if there are, but I would certainly like to have maybe a way for REST clients to consume some of the methods. I see the HTTP server but I have never used it, so far I have only used TCP And SuperTCP transports…

I think it is better for the session to not depend on the client setting up parameters on the messages and so on (which might not be even possible with some of the transports?), but handling all of that on the server (and hence the idea of sending a token parameter in the methods that need authentication) but I don’t know if this is correct/better, or what are the best practices / recommendations for this kind of uses?

Thanks in advance

Why do that, rather then relying on the authentication and session management that is built into Remoting SDK and works seamlessly, specifically to not have to burden the service API with having to pass such a token to every single method (and needing code on the client and server to handle it for every single implementation and call)?

Hello Marc,

Thanks for the reply. I read what I found on the documentation page and I got something similar than what you are saying here.

But… I must be doing something wrong or different somewhere, as I have always had to manually set/configure the ClientID/SessionId from the client for the requests… I need to review what I have been doing and find the problem/differences.

Something that I did not understand fully from the docs: for transports that don’t persist the connetion, say, HTTP or TCP, every call will internally generate the OnLoginNeeded event, and cause the Login service to be called every time? Maybe that’s where my confussion begins, or the reason for why I choose the actual (manual) way for handling this…

Thanks

All you need to do, really, is set RequireSession to true (via property or attribute) on all your services, except the Login service.

Any call to these services Weill then be rejected, if the client dies not have a session yet, with an ResskionNotFound exception, which is caught by the client app automatically, giving you a chance to perform login if needed via the OnLoginNeeded event. If you implement OnLoggingNeeded to log in successfully, the client infrastructure will automatically retry the failed call afterwards, meaning at the point where your client calls the server, you need not worry about authentication at all.

You can choose to explicitly call your Login service first, as part of your client flow, or just purely rely on OnLoginNeeded — depending n your needs and how it works best in your UX flow.

In the Login (and Logout) methods in youer Login service, all you need to do is create session if login is successful, ofc), by writing any fandom value ti the Session object; and to destroy set session in Logout, if needed.

Neither :slight_smile:

Persistent/keep-alive connections only the efficiency of the network; an active session persists across/between different physical TCP/HTTP connections (can in fact even persist across round-rosining between servers, when using a distributed session manager).

OnLoginNeeded only triggers if you attempt a call that requires a session, but there is none; and, as mentioned above, if it succeeds, the original call will seamlessly be retried. Manual/explict call to Login (outside of OnLoginNeeded) is optional, but may be advisable to avoid the extra round-trip on the first call (for example if you are sending large data, and don’t want to have to resend it twice).

How does this work?

Basically, on start/initialization, the RO client component creates a new unique client id (session id), that is sent along, transparently with each request to the server. this id changes when you restart your client (or create a fresh set on service/channel/message components; I forgot which one of these maintains the session id, right now. in think the message ;), but otherwise remains the same as long as your client app lives. You could even persist it to disk and restore the previous client id on restart, though I’m not sure I see a huge upside to that, vs just re-logging in, unless you expect your app to restart all the time, and you dont wanna accumulate excess sessions on the server, maybe)

On the server, each request that needs a session (whether via RequireSession, or you can of course also check this manually) looks up a session stored in the session manager with that ID. If none is found, “RequireSession” serves reject the call. If one is, they don’t.

Sessions persist in the serve based on your selected SessionManager, and its settings; by default, they are in memory (ie sessions go away when the server restarts), and also time out (I believe) 20 minutes after the last call with the matching client id. You can change these settings, or use different session managers that can persist sessions across server restarts or even share session between different servers (ie Login might get called on Server A, and a subsequent call to Server B will succeed because both servers see the session.

I am sure there are corner cases this scenario does not cover, but if I am honest, I cannot think of any; the Remoting SDK session system should be able to cover just about any authentication need without the need to roll your own system on top of it.

Thanks for the detailed answer, Marc.

I will make some tests and see how it goes. I have been using my “custom” way of handling sessions for a while now, and I don’t remember if that was because I found something that didn’t work for me, or if it was just lack of knowledge.

My “native” apps (I mean, windows desktop apps) usually have only one connection open to the server, and that connection is kept alive while the app is running, so I don’t see any issue regarding the OnLoginNeeded triggers, as the ClientId would be the same all the lifecycle of the app.

But for web apps I have a “data module handler” that has a stack of “datamodules” (i.e., classes that have the transport, service instances, and so on) and the app code asks for one datamodule when it needs one, and returns it to the stack when finishing. I think, for what I understood, that this will result in “mixing” of the sessions as the Transport/Message would have a session that might not correspond to the actual user sessions (as they are reused between calls), correct?

In this case the best would be… create the datamodule and store it in the client’s session data, instead of a global stack that would be reused between different calls… correct?

Thanks!

If different logical users would get/reuse the same data module from a pool (user being, say, the end user in the web browser, and the pool of data modules being maintained on the webserver acting as a “middle tier” between user and your RO server, then yes, tis would be an issue. Options would be to (a) reset there session ID when it is returned th the pool (so next use requires new login) or — if feasible – having the web server maintain a client ID per user, and reinitialize the data module’s client id (= session id) based on the user that’s using it, as it gets pulled from the pool.

obviously you’d want to avoid multiple users (or web requests) using the same data module at the same time in this scenario, even if otherwise it wold the re-rentrant and thread-safe.

That would also be an option, depending on your scaling needs (i.e. would it be feasible/desirable to keep each user’s data model alive?

Yes, if you have few users who make many requests, but maybe not if you have many (semi-simultaneous) users, that make relatively few or spaced-out requests.

Say you got 600 users, making a 1s request about every minute (assuming even random distribution across time). with pooled data modules, you need ~10 instances, with per-user data modules, you need 600.

Ok, so this is exactly what I am doing. I store the ClientID in the Session object of each web client, and set it when getting the DataModule instance for the web client to use.

For what I can see, the only thing I am missing is having the RequireSession on the services that do require a session. Right now I manually check at the beggining of each method if there is a valid registered (i.e., the user logged in) session (on stuff stored on the Session array), and of course, having the OnLoginNeeded on the client part to handle that, and use the retry functionality RO already has.

Thanks for your help. I think I will have more questions on the HTTP API part regarding this handling of sessions/logins but I still have some work ahead before getting into that.

Perfect. instead of sending it as separate parameter, though, just set there ClientID if there Message (?) in your DataModule to it, and it’ll be used as RO’s native Session ID/Client ID.

any time!

No worries, I’ll be here!

Hello Marc,

Sorry for the bump to an old thread, but I think it’s somewhat better to continue this subject here than creating a new thread.

I am starting to play with the HTTP API facilities provided by RO. There, anything could be a client, so, regarding the handling of sessions and so on, what’s the recommendation in this case?

For what I can see, I will need anyway to implement other Services/Methods to be able to have the RESTful API in my server, as, so far, most of my methods return the values needed as [out] parameters, which are not supported. And the parameters themselves for the methods are very limited in the data types they can use, correct? I can’t just ask for a struct defined in my RODL as a parameter (as I do now with my “internal” RO-only API), for what I got from the docs. Or maybe RO does some magic and convert everything to JSON? I haven’t got to the point of trying.

But, if that’s the case and I do have to create some new services and/or methods for this, I would like to know what would be the best way to handle the sessions. I see there is an Authentication property on the HTTP dispatcher, I guess specifically to handle this needs, but I don’t quite get how should it be implemented.

Thanks!

Hi,

HTTP API supports bearer authentication.

we have the HttpApi Authentication sample that demonstrates it.

I can recommend to grab HttpApi metadata and open it in swagger editor. it can generate CURL requests.

for example this request

curl -v -X "POST" "http://localhost:8099/api/login/login" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"NewParam\": \"a\", \"NewParam1\": \"a\"}"

will return authentication token

< Access-Token: {D1E72C34-2F03-4CB1-8256-484864E80E97}

note: specify -v key for seeing Access-Token

it can be used later for requests:

curl -v -X "POST" "http://localhost:8099/api/test/getdate" -H "accept: application/json" -H "Access-Token: {D1E72C34-2F03-4CB1-8256-484864E80E97}"
"2023-06-21T08:50:37.780Z"

You are limited by json types, however RODL types (simple types, arrays, struct, binary) can be converted to JSON w/o any issues so you can return your [out] parameters as a part or result struct.

in some complex cases, you can return Binary that can contain any data.