Class FederatedAssertionTokenSource

java.lang.Object
co.ankatech.ankasecure.sdk.auth.CachingTokenSource
co.ankatech.ankasecure.sdk.auth.FederatedAssertionTokenSource
All Implemented Interfaces:
TokenSource, AutoCloseable

public final class FederatedAssertionTokenSource extends CachingTokenSource
Presents a token some OTHER authorization server issued to this workload - Auth0, Entra, Okta, Keycloak, Cognito, or anything else the deployment has declared it trusts.

ANKASecure is still the relying party; where the token comes from is the provider's business

The PLATFORM holds no client registration and no credential at any external authorization server: server-side, the only thing it ever fetches from one is a JWKS, to verify a signature. That has not changed. What changed is that the WORKLOAD's own credential can now be presented by this SDK on the workload's behalf - ClientCredentialsExternalTokenProvider performs the client-credentials grant against the workload's own issuer with the workload's own client secret, and never with anything of ANKASecure's.

This source cannot tell the difference and deliberately does not try. It asks an ExternalTokenProvider for a token and carries it across one hop; whether that provider read the token from a file, ran a command, was handed it, or minted it is the provider's own business. That is why the SDK still ships no vendor code: what a fetching provider knows about an authorization server it reads from that server's own discovery document.

The token is opaque, all the way through

Nothing here reads it. Its issuer is not checked, its audience is not compared, its expiry is not consulted, no JWKS is fetched. Every one of those is the relying party's decision and the relying party is the server: it resolves the issuer, verifies the signature against trust material it admitted, and matches the subject to an actor binding. A client that pre-judged would be reimplementing a decision it does not own and would leak, through the shape of its local failures, which of the server's checks would have failed.

The consequence is worth stating plainly: a rejection arrives as one opaque 401 invalid_client, identical for a wrong issuer, an unbound subject, a disabled issuer, a bad signature and an expired token. That is the server's design, not an omission here - the costs of the credential kinds are equalised so a caller cannot time-probe which kind a given client_id uses. What the SDK CAN report precisely is a failure to obtain the external token in the first place, and it does, as ExternalTokenException.

Asked for on every mint, and discarded at the provider on a refusal

This source caches no external token of its own. It asks the provider on every mint, so a provider that re-reads an out-of-band file sees the current contents, and a provider that caches a token it minted answers from its own cache under its own stated lifetime.

The second case needs one thing this class must supply, because nothing else can. A provider that caches decides freshness against the CLIENT's clock and a skew, and the platform's refusal is one opaque 401 invalid_client that names no cause - so a client clock behind the issuer's would hold a server-dead token for the rest of its stated lifetime, twenty-four hours on some defaults, and re-present it on every attempt with nothing anywhere reporting why. So a 401 calls ExternalTokenProvider.invalidate() before the refusal is rethrown, and the caller's existing single retry then arrives with a freshly obtained token. The bound is structural: one re-fetch per failed mint, never a loop.

  • Constructor Details

    • FederatedAssertionTokenSource

      public FederatedAssertionTokenSource(co.ankatech.ankasecure.openapi.client.AnkaSecureOpenApiClient client, String clientId, ExternalTokenProvider provider)
      Parameters:
      client - the transport to mint against
      clientId - the ANKASecure actor UUID this external identity is bound to. Required even though the assertion names a subject: the platform resolves the actor before it parses anything the caller supplied.
      provider - where the external token comes from
      Throws:
      NullPointerException - if any argument is null
  • Method Details

    • mechanism

      public AuthMechanism mechanism()
      Description copied from interface: TokenSource
      Names the credential kind this source presents. Diagnostic only; nothing branches on it.
      Returns:
      the mechanism; never null
    • provider

      public ExternalTokenProvider provider()
      Where this source reads its external token. Exposed for diagnostics and for the containment check that asserts the source in force is the configured one.
      Returns:
      the provider; never null
    • invalidate

      public void invalidate()
      Discards this source's ANKASecure token AND anything the provider cached.

      Both, because a caller invalidating after an external secret was rotated would otherwise get a fresh ANKASecure mint built from the same stale external token, which is the failure it was trying to clear.

      Specified by:
      invalidate in interface TokenSource
      Overrides:
      invalidate in class CachingTokenSource
    • close

      public void close()
      Closes the provider, then this source.

      The provider retains credential material of its own - an external client secret, a token it minted with it - and after AuthenticatedSdk.close() nothing else would ever reach it, so both would survive in the heap for the life of the JVM.

      In a try/finally rather than in sequence: a provider whose close() threw would otherwise skip the bearer-token clear, and the session would end with the transport still holding a live credential.

      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface TokenSource
      Overrides:
      close in class CachingTokenSource
    • toString

      public String toString()
      Names the actor and the SOURCE of the token, never the token.
      Overrides:
      toString in class Object