First post - please at least try and be gentle… Before I get to the specifics of my question, I suspect that I might need to prefix this with the acknowledgement that what I’d like to do (as expressed here) might be a bit unusual. I’m treating this as a learning exercise, not just a search for a solution to specific problem.
It might also be relevant to mention my experience and skill level, which is moderate. I’ve got a reasonable amount of experience with PHP in writing web apps, but am still learning the basics of Python. One of the reasons I’ve turned to Python is because the PHP-GTK project seems essentially dead in the water, while Py-GTK looks to be going from strength to strength.
OK, so here’s the question…
If I’m writing in a web paradigm and I want my web app to authenticate users to my platform, my use of the web model makes this easy for me. Code on the web server runs under a local account - and I give that local account access to a back-end SQL server such as MariaDB. The server account can have broad rights to the database - and I can have a user table in that database that contains salted hashes of user-specific passwords. I do not need to register all my application users on the database.
However, if I want to replace a thin web client with a thick client, written in Py-GTK, then I need to think about how to authenticate my users to my database. As an added spin, one of the things that would be nice to achieve would be a model in which the back end database can be used with both thick and thin clients without modification.
I want my thick client users to be able to access the data, with the same IDs and Passwords that they would use via the web app. But in order to do that, I’m going to need a way of unlocking the database for the thick client.
The simplest, dirtiest and least secure way to do this would be to have some form of .ini file on each client, with an “application level” password embedded in that file in some way. Problem is, that isn’t secure, because my local user could simply scan the file for the password and from that get full rights to the application’s data.
So far, I’ve come up with one alternate method, but I’m concerned that it might be clumsy, fragile, or not scale well. This would be to use Python’s socket library and write a network daemon to be co-located on the database server. The authentication paradigm would then be as follows:-
- Daemon starts on database server (or an intermediate app server, if performance/scaling/separating for security makes sense).
- User launches thick client written in PyGTK.
- Thick client presents user with login screen.
- User submits ID and Password to thick client.
- Thick client submits credentials to server via a sockets call.
- Server side Python code, running under a local ID, authenticates to the database, retrieves user record and validates password.
- If the credentials match, the server side code creates a session token and passes this back to the client as the socket reply.
- Server side code also caches the token - i.e. in memory, with a defined TTL (Time to Live).
- User continues to interact with application, now submitting each server-side request prefixed with the provided token.
Now, there are a bunch of other problems to solve here. I’d need to gracefully handle an expiring token if a user tries an end-run around session time limits. I’d need to protect against spoofing, likely by having some form of certificate-protected exchange - either by using TLS directly on the socket, or by coding that in to my server-side wrapper. I can strengthen this by doing things like hashing the client IP address in to my token, for example.
Perhaps the most interesting challenge will be crafting the protocol between client and server logic. I’ve done some really primitive prototyping and learned I can use something like
client_request = conn.recv(1024)
(where “client_request” is the variable into which I want to deliver the client command and 1024 is the maximum length of data I am expecting the server to receive) to handle client-to-server traffic, then
conn.sendall({server_response})
(where server_response is the returned data) to get a reply back to the client. The $64,000,000, however, is: will this form a viable foundation, or am I building on a weak/ill-advised foundation? Is there a better way?
Lots of other questions start popping up… Things like:-
-
Is there a viable upper limit on the transfer size?
-
Does it make more sense to break up result sets and send them as discrete rows, with a set terminator?
-
If I break up the result sets, how do I handle them efficiently on the server side code?
I don’t expect to answer all these questions here and now. I’m also very conscious that people will read this and think, “Hmm… Bad idea. There’s a better way”. I’m very much interested in “better ways”, but please bear in mind I’m still at “early days” on this journey.
The reason I’m asking here and now is that I’ve reached a point in my Python education where I’m starting to experiment with small chunks of code that will contribute to this larger project - and I desperately don’t want to learn bad habits or take a wrong turn that will turn around and bite me later.
I’m happy to learn and grateful for advice. I know that the problem I’m trying to solve is narrow and specific and your answer might be, “Hmm, I wouldn’t set out to do that at all…” So if you can suggest a way of handling the authentication for a thick client in a way that’s reasonably secure, difficult to defeat on the client side… and not too difficult for a relative newbie to understand, then I’d be grateful for advice.
Thank you!