Credential Coordinator
The goal of the Credential Coordinator is to act as the central mechanism that orchestrates iteractions between all components of the token management subsystem. This is one of the rare classes in the Okta Client SDK that doesn't support overridden capabilities, because all the properties and components it interacts with are the primary interfaces which are used by developers to override business logic.
The coordinator is essentially a traffic cop that enforces thread safety, type conformability, error handling, and validation of data that passes through it.
Token storage and credential access are the most vital portion of the SDK, and ensures the stability of the ecosystem. As a result, this component is the most important to get right.
The coordinator encapsulates a variety of workflows, which are often asynchronous and requiring thread safety, so these access patterns are important to get right.
Furthermore, this class only represents the interactions between several child interfaces, all of which a developer can override. As a result, care must be taken to ensure the consistency of data passing through those interfaces to account for errors in a customer's implementation. Default implementations of these interfaces will be built in to the SDK, and will be enabled out-of-the-box but since these can be reassigned by a developer, the SDK should ensure that reassignments of these values do not result in security or stability gaps.
The SDK does not, and should not, enable runtime changes to these overridden interfaces. Changes to these properties should be restricted to an initialization or startup phase of a customer's application, but since different environments classify these phases differently, it's not something the SDK should enforce.
As a result, it's acceptable to indicate that changes to these properties after application initialization should be considered "Undefined Behavior", and not generally supported.
Primary workflows
This class orchestrates interactions between a variety of business-logic components that form the basis of the token management subsystem. This is mostly used to handle the underlying implementation of the Credential class static functions and properties, which involve interactions between the TokenStorage and CredentialDataSource interfaces.
Store tokens
Storing tokens involves interacting with both TokenStorage and CredentialDataSource interfaces.
Note that when a token is stored, there is the possibility that it will implicitly change the default token. This is because if a token is stored when the storage is empty (e.g. no tokens yet stored), this first token being stored automatically becomes the default token.
There is the potential for data races and threading considerations in this logic for a number of reasons, not the least of which is that the default credential may be evaluated by the developer while the token is being stored, the default identifier may be actively changing in storage, or the credential instance may not yet be created in the datasource.
Care must therefore be taken to ensure multithreaded consistency is maintained.
The default Credential property
The default credential is a little bit like magic because it can be:
- Explicitly set by the developer
- Implicitly set when the first token is stored
- Changed between multiple stored credentials to swap between identities
This property therefore represents the following operations:
- Get a Credential using the default value provided from storage.
- Set the storage's default ID from a newly-assigned Credential.
- Implicitly assign a Credential when the TokenStorage instance sends a delegate event indicating the default ID has changed.
- Lazily load the default credential when it is first requested after an app launch.
Lazy loading
When an application starts, it's important to limit the amount of code executed at startup time. This is especially important for loading of the default credential, because:
- Application launch time is critically important to customer applications
- The app developer may not yet know which configuration the app needs to run in
- Overrides to key interfaces, such as TokenStorage, may need to first be performed
- The default credential may be stored with biometrics, which on some platforms would trigger modal prompts to the user which could prevent the app from starting cleanly
Therefore the default value should only be retrieved when it's first requested by the developer, lazilly retrieving the value from storage.
Fetch all IDs
This is simply a wrapper around the TokenStorage allIDs property.
Fetch a Credential by Token ID
Similar to storing tokens, but instead an existing token is fetched from TokenStorage before it's passed to the CredentialDataSource.
Find Credentials matching a filter
This is a little more complicated, where the list of allIDs is retrieved, and the metadata for each token ID is used to filter a list of matching tokens.
The workflow can and should be optimized using map/reduce/filter operations as appropriate for the language or environment.
Remove a credential from storage
This removes a Token from storage, simultaneously ensuring the CredentialDataSource removes its in-memory cached Credential representing the token.
Updating Credential instances with refreshed tokens
A Token may be refreshed from different places within an application, and while attempts are made to ensure only a single instance of a Credential is defined at runtime, it's still possible for a Token to be refreshed independent of this workflow.
As a result, one of the Coordinator's jobs is to observe notifications / events sent when a token is refreshed, and to update the token's value in TokenStorage so it's safely persisted.
Broadcast events
Certain circumstances are important to multiple actors in a system, so for maximum flexibility, some events should be broadcast when these occur, such as:
- Default credential changed
- Credential created
- Credential removed