How do I obtain the authenticated user in the back end?

#1

Maybe I am missing something, but I have no idea how to pass an authenticated user record from the authenticator to the back end. Basically I am using a simple CRA authenticator that does a look up in a table for a matching client ID and then ensures the client has a matching key. Later I make an RPC call that goes to the back end and the database needs to be updated with the client’s user record.

Since I cannot trust the parameters the phone sends me, the only secure way to do this is to access the authenticated credentials as checked by the authenticator but I have no idea how to access this from the back end. Please help, thanks.

0 Likes

#2

Hi Malibu, I guess there are many ways to do this depending on your setup, some possible scenarios;

a. Authenticator has access to the back-end’s database

On authentication, store the session id and authentication details in a table (“sessions”), this will then be available to the back-end whenever it needs it. (i.e. table lookup based on incoming session_id)

b. Back-end has a WAMP connection (preferred);

On authentication, store the session id and authentication details either in memory, or in a local database table (eg; zlmdb), then implement an endpoint to query this information based on session id. Then add some authorization to the endpoint such that only the back-end can successfully call it.

(I’m assuming here that your simple CRA authenticator is a “custom authenticator” rather than Crossbar’s built-in static authenticator)

Note; obviously when dealing with credentials like this you need to be really careful with authorization, implementing an end-point to recover credentials without being 101% sure it’s secure is not recommended … :wink:

If the back-end needs prompting when users log in / out, subscribe it to “wamp.session.*” …

0 Likes

#3

Thank you for your quick answer.

Maybe I didn’t use the correct terminology. What I am saying is that I have a dynamic authenticator, a dynamic authorizor, and a back-end client session creating rpc calls and subs behind both of these. Without checking the key again, the back-end client session cannot be sure who is talking to it unless it has some way to refer back to the result of the original authorization.

What is the common way for back end clients to know the current client authid? I have an old autobahn-python installation and I just pass the client id into the initialization of the call handling classes at authorization time. How is it done in crossbar?

0 Likes

#4

If the way is to create a database entry with the session ID then I can do that, though I am not too keen on creating another table and more database interaction for this. I would be surprised the authorization layer has no way to pass through an in memory construct somehow.

0 Likes

#5

What would you expect Crossbar to provide here?

If the main point is to “hide” some RPC calls (or publication topics) so that only authenticated clients can call them, that is handled by the permissions system. For example, you could have a special “role” that your dynamic authenticator assigns to authenticated sessions.

0 Likes

#6

Ok, so if you just want the authid, and you’re happy with an inline lookup in terms of performance, you can get it by calling “wamp.session.get” with the relevant session_id … is this what you were after?

If you’re just looking to work with WAMPCRA this may well do the job for you, but worth noting that if you decide to implement “other” forms of authentication (for example I use Google oAuth2 for a lot of stuff) then you may well acquire other information as part of the authentication process that will not exist as a part of the session. I was (maybe mistakenly) assumed this was where you were going when you said “credentials” …

So if you’re using oAuth2, you’ll have this (or something similar) in your custom auth;

try:
    info = client.verify_id_token(ticket, clientId)
except crypt.AppIdentityError:
    raise_error(self.INVALID_TICKET)

And info is a rather useful object which contains things like the preferred email address (which may be different to the authid) and the user’s full name (amongst other things). This tends to be the bit that’s not so easy to get at …

0 Likes

#7

If the best way to do this is to store a table row on the session then I can do that. So in ApplicationSession I just call self.session.get?

As for what I expect from Crossbar, I expect the non-secret autentication credentials to be visible to all the code so that I know what individual client I am working with. Maybe I am missing something about how to design this, but my back end client needs to update database rows on behalf of the connected client. It cannot simply ‘accept’ the ID being passed in a parameter because any parameter could be spoofed. Without some link back to the authentication you can never know the individual client you are talking to.

Yes I know the client I am connected with has access to the role, but I still cannot trust that they are the individual they say they are without referencing the very authid they authenticated with. The only way I can think that this would work is for my back end code to also have a CRA auth; but then what would be the point of doing it in crossbar?

If there is something about this architecture that I am missing, please let me know.

0 Likes

#8

If you make sure disclose is enabled (https://crossbar.io/docs/Caller-Identification/) then RPC endpoints will be supplied with keyword arg called “details”, which will have an attribute called “caller”, which will be the “session_id” of your client. If you use this with “wamp.session.get” (https://crossbar.io/docs/Session-Metaevents-and-Procedures/) it will return an object containing the caller’s authid …

When you say “non-secret autentication credentials”, within the context of your “wamp-cra” authentication, do you just mean the information returned by “wamp.session.get” ?

0 Likes

#9

Well wamp CRA basically involves an ID and an unknown key. The ID should be public and the key private should it not? Therefore I would expect crossbar to retain the ‘sanctioned’ ID as it is very important to the back end to know who the individual is. In my environment, the server selects an ID randomly for the client and they all stay with that ID (and various levels of anonymity to the ID). Without it I do not know the individual.

0 Likes

#10

The only “sanctioned ID” that crossbar knows about is the authid for the session (which your dynamic authenticator returns). A registered procedure that asked for “details” can retrieve the authid of the caller as oddjobz outlines above.

0 Likes

#11

Ok, so the id (authid) is held in the session and available via wamp.session.get, based on the session_id which you can get from the “details” arg … so on any RPC endpoint, if you need the authid you would have something like;

session_details = yield self.call('wamp.session.get', details.caller)
print(f'Authid={session_details.get("authid")})
0 Likes

#12

Ok thank you very much for the help. This sounds like what I was looking for. I was trying to get this from the documentation from the long time but it just wasn’t clear to me. If I ever use another form of authentication obviously I will need to rework this, but as long as the client can pass the ID to the auth layer and it can all be authd then I can always use the backend DB method to confirm the client_id at auth time and ‘sanction’ it that way.

0 Likes

#13

No problem, maybe something we can clarify in the docs … :slight_smile:

0 Likes

#14

Hi, I put your recommended code in the ‘onJoin’ method of my backend client process, and I get the following error:

builtins.AttributeError: ‘SessionDetails’ object has no attribute ‘caller’

I also tried the same with ‘authid’ and ‘session’. There is ‘session’ in the SessionDetails but when I try to do:

session_details = yield self.call('wamp.session.get', details.session)

It tells me there is no sessionid that matches in the router.

Not sure what I am doing wrong. My only authorizer returns: {“allow”: True, “disclose”: True} and the definition for the call in the config has the same.

0 Likes

#15

Ok, so I have a back-end RPC stub called test;

def test(self, details=None):
        print('---------------')
        print(f'details.caller: {details.caller}')
        print(f'details.caller_authid: {details.caller_authid}')
        print(f'details.caller_authrole: {details.caller_authrole}')
        print('---------------')

The output when it runs (i.e. when the endpoint is called) looks like this;

2019-05-13T02:53:00+0100 ---------------
2019-05-13T02:53:00+0100 details.caller: 3208655472799514
2019-05-13T02:53:00+0100 details.caller_authid: oddjobz@ ...
2019-05-13T02:53:00+0100 details.caller_authrole: user
2019-05-13T02:53:00+0100 ---------------

Is it possible you are confusing back-end and client? The onJoin in the client will return a sessionDetails object that contains a session id … ???

0 Likes

#16

I have this python script set up as a BackendSession in the config.json with a single Authenticator and Authorizer. The Authorizer returns { ‘allow’: True, ‘disclose’: True }.

The backend session is set up like this:

class BackendSession(ApplicationSession):

    def my_handler(self, message):
        print("HANDLER:{}".format(message))

    def __init__(self, *args, **kwargs):
        super(BackendSession, self).__init__(*args, **kwargs)
          <db session setup>

......
    @inlineCallbacks
    def onJoin(self, details):
        print("Backend session joined: {}".format(details))
......
        def desktop_query(tag_name, company_id):
            print("SESSION: {}".format(details.caller))
            return json.dumps({'result': 'success'})
......
        try:
            reg = yield self.register(desktop_query, u'biz.gemini.desktop_query')
            print("procedure tw_desktop_query() registered")
        except Exception as e:
            print("could not register procedure: {}".format(e))

For some reason the desktop_query handler never gets details.caller.

Also, I’m on crossbar==18.9.2 could that be an issue?

I’ll look into this more tomorrow. Really trying hard to figure this out.

0 Likes

#17

Mm, without wanting to think too hard about your scoping, in principle your issue is;

def desktop_query(tag_name, company_id): 

should be;

def desktop_query(tag_name, company_id, details=None):
0 Likes

#18

If you’re not getting anything in “details” at the desktop_query level, you may need this too;

from autobahn.wamp.types import RegisterOptions
options = RegisterOptions(details_arg='details', invoke=u'roundrobin')
reg = yield self.register(desktop_query, u'biz.gemini.desktop_query', options)

i.e. pass options when you register the endpoint, and in options specify the name of the argument you want to use (i.e “details”)

0 Likes

#19

Ok, just to clarify all this, I’ve documented a self-contained worked example here;

Hopefully this will clear things up a little …

0 Likes

#20

OK so I still don’t have it yet, and unforunately the demo doesn’t help much. All of the last comments were to obtain SessionDetails and that isn’t what my problem is. My problem is that SessionDetails doesn’t have ‘caller’ or any ‘session’ that lines up with the ‘session’ that the authorizer has.

Some interesting findings:

  • For me when I do options=RegisterOptions(details=True) the argument is not there but it is in the docs
  • When I do options=RegisterOptions(detail_arg=details) it fails with some exception but there is no message.

But again, I have SessionDetails but I need SessionDetails.caller, or I need to know how to get the same SessionDetails.session that the authorizer sees so that I can make a table entry on the back end.

A word about the scoping I chose:
Normally I don’t inner-scope function calls but in this case I thought it would help since it creates a function that has access to everything onJoin does.

Also, I’m confused why in the demo the function is on_join but in the demo I worked from in the site it uses onJoin… Are we not overriding the same calls for BackendSession?

0 Likes