Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oauth behavior #1739

Open
mhavey opened this issue Jan 29, 2025 · 6 comments
Open

Oauth behavior #1739

mhavey opened this issue Jan 29, 2025 · 6 comments

Comments

@mhavey
Copy link

mhavey commented Jan 29, 2025

Recently support was added for OAuth context. It accepts a token (JWT token?). I assume the client passes this token when making a call to the app server supporting OAuth external security. A few questions:

  1. Is there a way to pass a new token if the one I was using expired? Unlike SAML context, there are no callbacks for oauth.
  2. Suppose I want my Java application to support many end users, each possessing a token. Do I have to create many DatabaseClient objects? Seems like the current approach works best if the client is created for a designated service user.
@rjrudin
Copy link
Contributor

rjrudin commented Jan 29, 2025

Hey @mhavey - for expired tokens - yes, you would need to manage this yourself. Our experience with supporting token renewal for Progress Data Cloud - you can see the code here - is that this is best done via an OkHttp interceptor. With an interceptor, you can detect the condition that indicates the token has failed, regenerate a token, and then retry the request with the new token. This examples shows how to add a custom OkHttp interceptor to the DatabaseClient.

For your second question - that's correct, since a DatabaseClient is bound to a single authentication context, you would need for each separate SAML token.

@mhavey
Copy link
Author

mhavey commented Jan 29, 2025

Thanks Rob. A couple followups.

  1. Is it possible to intercept the request before it is sent to MarkLogic and modify the header to add the token? That way I can use the same client but sneak in a user's JWT token just in time. It would override the existing auth context for one call. Seems like a bit of hack anyway. Just checking.
  2. Rather than creating a client up front, create a new client each time the user interacts. The release it as soon as the call is done. Seems reasonably if each call is stateless. Is there a lot of overhead in creating the client? Is this an antipattern?

@rjrudin
Copy link
Contributor

rjrudin commented Jan 30, 2025

Item 1 above works well - I should have mentioned that upfront. Most of our auth support in the client depends on an OkHttp interceptor doing exactly what you describe. In that sense, you could use the OAuth context with a "dummy" token, and then customize an interceptor to do whatever you would like based on the user, expiration etc. You have full access to the request in the interceptor.

You can create clients like that, but there is some overhead in terms of what OkHttp does under the hood to e.g. set up a connection pool. I think your first idea is the way to go, and it's definitely not a hack.

@mhavey
Copy link
Author

mhavey commented Feb 4, 2025

What I'd like to do is create a single DatabaseClient with an OAuth context set to a dummy token. Then for each request, I would like to change the Authorization header of the request to the ACTUALl token of the end user.

There are MANY end users, each having their own token. They share a DatabaseClient, but I'm hoping to effectively ignore the auth context associated with the client and override ON EACH REQUEST the authorization header. For example, suppose end user 1 has token t1 and end user 2 has token t2. When user 1 wants to make a request to MarkLogic (e.g, get a document), I want to set the Authorization header of the HTTP request to user 1's token, which is t1.

It's not hard to write the Ok HTTP intercept do change the header prior to sending the request. The examples you provided show how to do this.

The challenge is: how do I know the token in the intercept itself. The intercept's argument is chain, and from this I know the request details: url, method, headers, body. I have no way to tie the request with the token of the end user.

An enhancement that would help me do this would be a correlation ID. If I wanted to get a document, for example:

XMLDocumentManager docMgr = client.newXMLDocumentManager();
DocumentPage doc = docMgr.read(uri, corrId);

Then in the intercept, I can see corrId (perhaps as a header):

public Response intercept(Chain chain) throws IOException {
   String corrId = chain.request().header("CORRID")
   // now change Authorization header and proceed with request
}

Hope this makes sense. Happy to clarify if anything is unclear.

@rjrudin
Copy link
Contributor

rjrudin commented Feb 4, 2025

This is more of a Java question, and a common technique in Java applications to solve this problem is to use a ThreadLocal - https://www.baeldung.com/java-threadlocal . Spring Security does this to allow application developers to do something similar, which is accessing the authentication object for a particular thread (user).

What this might look like is that the entry point to your middle tier that authenticates a user will capture the token in a ThreadLocal object. Your OkHttp interceptor can then access that same ThreadLocal.

@mhavey
Copy link
Author

mhavey commented Feb 4, 2025

This is a good tip. I'm going to try it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants