Skip to content

[Fix #1544] auth Content to be completable future - #1545

Merged
fjtirado merged 1 commit into
open-workflow-specification:mainfrom
fjtirado:Fix_#1544
Aug 3, 2026
Merged

[Fix #1544] auth Content to be completable future#1545
fjtirado merged 1 commit into
open-workflow-specification:mainfrom
fjtirado:Fix_#1544

Conversation

@fjtirado

Copy link
Copy Markdown
Collaborator

Fix #1544

@fjtirado
fjtirado requested a review from ricardozanini July 16, 2026 09:37
@fjtirado
fjtirado force-pushed the Fix_#1544 branch 4 times, most recently from d48e441 to ceebc05 Compare July 16, 2026 11:21
@fjtirado
fjtirado marked this pull request as draft July 16, 2026 11:30
@fjtirado
fjtirado marked this pull request as ready for review August 3, 2026 11:45
Copilot AI review requested due to automatic review settings August 3, 2026 11:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the auth subsystem to support non-blocking authentication flows by changing AuthProvider.content(...) (and related token/provider APIs) to return CompletableFuture, and propagates that async behavior through HTTP/OpenAPI execution paths.

Changes:

  • Update AuthProvider.content(...) and AccessTokenProvider.validateAndGet(...) to return CompletableFuture, and adjust built-in auth providers accordingly.
  • Refactor HTTP request execution to be future-based end-to-end (including auth header injection) and move OpenAPI parsing onto the workflow executor.
  • Refactor JWT representation (replace impl-specific class with a core JWT record) and update/add tests.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
impl/test/src/test/java/io/serverlessworkflow/impl/test/CustomAuthProviderFactoryOverrideTest.java Updates test auth provider override to the new async content() signature.
impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java Moves OpenAPI parsing to supplyAsync and composes HTTP executor application via futures.
impl/jwt-impl/src/test/java/io/serverlessworkflow/impl/http/jwt/JacksonJWTImplTest.java Updates JWT tests to align with the new JWT representation/claim access patterns.
impl/jwt-impl/src/main/java/io/serverlessworkflow/impl/executors/http/oauth/jackson/JacksonJWTImpl.java Removes the old JWT implementation class (replaced by core JWT record).
impl/jwt-impl/src/main/java/io/serverlessworkflow/impl/executors/http/oauth/jackson/JacksonJWTConverter.java Instantiates the new core JWT record during token conversion.
impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/RequestExecutor.java Changes request executor contract to return CompletableFuture<WorkflowModel>.
impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java Delegates directly to async RequestExecutor rather than wrapping in supplyAsync.
impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java Adapts token provider API to return CompletableFuture<JWT>.
impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractRequestExecutor.java Adds async auth header injection and returns a CompletableFuture request result.
impl/core/src/test/java/io/serverlessworkflow/impl/auth/JWTTest.java Adds tests for the new JWT.toCollection(...) helper.
impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java Adapts resource loading auth header creation to async AuthProvider.content(...) via .join().
impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java Refactors script context construction to happen inside the executed runnable/callable.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/OpenIdAuthProvider.java Makes provider class public (and aligns with updated async base provider behavior).
impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuth2AuthProvider.java Makes provider class public (and aligns with updated async base provider behavior).
impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java Replaces interface with a record and adds helpers for time/collection claim decoding.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/DigestAuthProvider.java Adapts digest auth header generation to return a CompletableFuture<String>.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/DefaultAuthProviderFactory.java Adds protected factory methods to enable overriding provider instantiation.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/CommonOAuthProvider.java Returns async auth content by composing token provider futures.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/BearerAuthProvider.java Wraps bearer token computation in a completed future.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/BasicAuthProvider.java Wraps basic auth computation in a completed future.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/AuthProvider.java Changes content(...) to return CompletableFuture<String>.
impl/core/src/main/java/io/serverlessworkflow/impl/auth/AccessTokenProvider.java Changes validateAndGet(...) to return CompletableFuture<JWT>.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread impl/core/src/main/java/io/serverlessworkflow/impl/auth/BasicAuthProvider.java Outdated
Comment thread impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java
Copilot AI review requested due to automatic review settings August 3, 2026 12:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Suppressed comments (2)

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java:62

  • validateAndGet() now returns CompletableFuture<JWT>, but it still throws exceptions synchronously (e.g., issuer validation or token fetch failures). That breaks async composition because callers may never receive a failed future (the exception escapes before a future is returned).
  public CompletableFuture<JWT> validateAndGet(
      WorkflowContext workflow, TaskContext context, WorkflowModel model) {
    Map<String, Object> token = invoke(workflow, context, model);
    JWT jwt = jwtConverter.fromToken((String) token.get("access_token"));
    if (issuers != null && !issuers.isEmpty()) {

impl/jwt-impl/src/main/java/io/serverlessworkflow/impl/executors/http/oauth/jackson/JacksonJWTConverter.java:39

  • JWT parsing should validate the expected 3-part structure (header.payload.signature). The current check allows unexpected part counts and the error message mentions ':' even though the separator is '.'.
    if (parts.length < 2) {
      throw new IllegalArgumentException(
          "Invalid JWT token format. There should at least two parts separated by :");
    }
    return new JWT(token, fromPart2Map(parts[0]), fromPart2Map(parts[1]));

Copilot AI review requested due to automatic review settings August 3, 2026 14:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java:52

  • type() uses Optional.of((String) header.get("typ")), which will throw a NullPointerException if the header contains the "typ" key with a null value. Prefer a null-safe fallback to the claim value.
  public Optional<String> type() {
    return header.containsKey("typ")
        ? Optional.of((String) header.get("typ"))
        : Optional.ofNullable((String) claims.get("typ"));
  }

impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java:117

  • auth.content(...).join() re-introduces blocking in the resource loading path. This can negate the benefit of the async AuthProvider API and can lead to thread starvation/deadlocks if the auth future is completed on the same executor that is currently calling load() (e.g., when load() is invoked inside supplyAsync). Consider adding an async load... variant (or otherwise composing the auth future) so callers can remain non-blocking.
            .map(
                auth ->
                    AuthUtils.authHeaderValue(
                        auth.scheme(),
                        auth.content(workflowContext, taskContext, model, uri).join())));

impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java:67

  • In the non-await branch, moving ScriptContext construction into the submitted task changes semantics: any failures in argumentExpr/environmentExpr/codeSupplier will now happen asynchronously and be effectively dropped (since the returned CompletableFuture is already completed). Previously those errors surfaced synchronously from apply(), and the script input was snapshotted at submission time. Consider constructing ScriptContext before submitting so invalid expressions/config are still detected and the context is deterministic.
    } else {
      workflowContext
          .definition()
          .application()
          .executorService()

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java:62

  • validateAndGet() now returns a CompletableFuture, but it can still throw synchronously (from invoke(), fromToken(), or issuer validation). This breaks async composition (e.g., CommonOAuthProvider.content(...).thenApply(...) will throw instead of returning a future completed exceptionally). Wrap the body and return a failed future on exception.
  public CompletableFuture<JWT> validateAndGet(
      WorkflowContext workflow, TaskContext context, WorkflowModel model) {
    Map<String, Object> token = invoke(workflow, context, model);
    JWT jwt = jwtConverter.fromToken((String) token.get("access_token"));
    if (issuers != null && !issuers.isEmpty()) {

Copilot AI review requested due to automatic review settings August 3, 2026 15:18
@fjtirado
fjtirado force-pushed the Fix_#1544 branch 2 times, most recently from 158d1f3 to fe11779 Compare August 3, 2026 15:19
… future

Signed-off-by: fjtirado <ftirados@ibm.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java:62

  • validateAndGet() now returns a CompletableFuture, but it can still throw synchronously (e.g., issuer validation failures or invoke()/fromToken exceptions). That breaks async call chains; wrap the body and return a failed future on exceptions.
  public CompletableFuture<JWT> validateAndGet(
      WorkflowContext workflow, TaskContext context, WorkflowModel model) {
    Map<String, Object> token = invoke(workflow, context, model);
    JWT jwt = jwtConverter.fromToken((String) token.get("access_token"));
    if (issuers != null && !issuers.isEmpty()) {

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java:27

  • JWT is now a record but it doesn’t enforce non-null components or immutability. Since JacksonJWTConverter builds mutable maps, callers can mutate header/claims (and nulls can later cause NPEs). Consider validating and wrapping maps as unmodifiable in a compact record constructor.
public record JWT(String token, Map<String, Object> header, Map<String, Object> claims) {

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java:52

  • type() uses Optional.of((String) header.get("typ")) when the key exists, which can throw NPE if the header contains typ=null (or ClassCastException if it’s not a String). Using String.valueOf + null checks avoids surprising failures while keeping behavior equivalent.
  public Optional<String> type() {
    return header.containsKey("typ")
        ? Optional.of((String) header.get("typ"))
        : Optional.ofNullable((String) claims.get("typ"));
  }

impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java:76

  • Typo in comment: “Me may refactor…” should be “We may refactor…”.
              // Me may refactor this even further to reuse the same executor (since the base URI is

Copilot AI review requested due to automatic review settings August 3, 2026 15:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (6)

impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java:117

  • Using auth.content(...).join() blocks the current thread. If content() completes on the same executor that is performing the load (or has limited threads), this can deadlock and it also wraps failures in a CompletionException, making error handling less explicit. It may be safer to introduce an async loadAsync(...)/loadURIAsync(...) path that composes the auth future instead of joining, and update call sites that already operate on CompletableFutures to use it.
            .map(
                auth ->
                    AuthUtils.authHeaderValue(
                        auth.scheme(),
                        auth.content(workflowContext, taskContext, model, uri).join())));

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java:88

  • toCollection(...) returns a raw Collection unchanged when the claim value is a Collection, even if it contains elements that are not instances of clazz. This can violate the method’s generic contract and surface later as a ClassCastException in consumers. Consider filtering/casting elements to the requested type.
    if (v instanceof Collection col) {
      return col;
    }

impl/core/src/main/java/io/serverlessworkflow/impl/auth/BasicAuthProvider.java:71

  • String.getBytes() uses the platform default charset, which can make the generated Basic auth token non-deterministic across environments. Prefer an explicit charset (UTF-8) when converting the formatted credentials to bytes.
                String.format(
                        USER_PASSWORD,
                        userFilter.apply(workflow, task, model),
                        passwordFilter.apply(workflow, task, model))
                    .getBytes()));

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractRequestExecutor.java:117

  • auth.content(...) is invoked synchronously. If an AuthProvider performs blocking work before returning its CompletableFuture (e.g., builds a completedFuture after doing IO), this will block the caller thread and can defeat the goal of async auth; it can also deadlock if called from a constrained executor. Consider offloading the invocation of auth.content onto the workflow executor before composing the returned future.
    String scheme = auth.scheme();
    return auth.content(workflow, task, model, uri)
        .thenAccept(

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JWT.java:27

  • JWT now stores the provided header/claims maps directly. Since JacksonJWTConverter builds mutable Map instances, callers can currently mutate jwt.header()/jwt.claims() which can lead to surprising behavior and makes JWT effectively non-immutable. Consider defensively copying to unmodifiable maps in the record constructor (and validating non-null inputs).
public record JWT(String token, Map<String, Object> header, Map<String, Object> claims) {

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java:72

  • validateAndGet(...) now returns a CompletableFuture, but it can still throw synchronously (e.g., from invoke(...), fromToken(...), or issuer validation). This breaks async composition (callers using thenApply/thenCompose may see immediate exceptions instead of an exceptionally-completed future) and can cause AuthProvider.content() to throw despite its async signature. Wrap the body in try/catch and return a failed future on error (optionally also offload the blocking HTTP call to an executor).
  public CompletableFuture<JWT> validateAndGet(
      WorkflowContext workflow, TaskContext context, WorkflowModel model) {
    Map<String, Object> token = invoke(workflow, context, model);
    JWT jwt = jwtConverter.fromToken((String) token.get("access_token"));
    if (issuers != null && !issuers.isEmpty()) {
      jwt.issuer()
          .ifPresent(
              issuer -> {
                if (!issuers.contains(issuer)) {
                  throw new IllegalStateException("Token issuer is not valid: " + issuer);
                }
              });
    }
    return CompletableFuture.completedFuture(jwt);
  }

@fjtirado
fjtirado merged commit 9fec75f into open-workflow-specification:main Aug 3, 2026
3 checks passed
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

Successfully merging this pull request may close these issues.

Make AuthProvider.content() return CompletableFuture for async auth flows

2 participants