-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
KTOR-7775 BasicAuth: Add clearToken method #4645
Conversation
70360f1
to
85bde92
Compare
* | ||
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.client.plugins.auth.providers.BasicAuthProvider.clearToken) | ||
*/ | ||
public fun clearToken() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make this method a part of AuthProvider
interface, but it would be potentially breaking change. Only if not declare an empty implementation by default.
However, it makes sense for me to make clearToken
a required for auth provider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could introduce it with a default implementation of throwing an UnsupportedOperationException
. Then we can drop the default implementation for the next major release as a breaking change. It's breaking the Liskov rule, but I think the pros outweigh the cons for having a standard function here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... If we add this method, we assume that all implementations of AuthProvider
should be caching, as this method doesn't make any sense for stateless implementations. Probably it would be better to introduce CachingAuthProvider
containing clear
, and providing a way to create custom auth providers using AuthTokenHolder
under the hood?
/**
* A base implementation for an authentication provider with authentication state caching.
*
* @param AuthState The type representing the authentication state, which contains necessary token or credential information.
* @param fetchState A function for fetching the authentication state, typically used during first access
* or during refresh (if [refreshState] is not overridden).
*/
public abstract class CachingAuthProvider<AuthState : Any>(
private val fetchState: suspend () -> AuthState?,
) : AuthProvider {
// If we go this way, AuthTokenHolder should be renamed to something else
private val tokenHolder = AuthTokenHolder(fetchState)
/** Returns the cached authentication state, or fetches it if it wasn't loaded before. */
protected suspend fun loadState(): AuthState? = tokenHolder.loadToken()
final override suspend fun addRequestHeaders(request: HttpRequestBuilder, authHeader: HttpAuthHeader?) {
val tokens = loadState() ?: return
addRequestHeaders(request, authHeader, tokens)
}
/**
* Adds an authentication method headers and credentials.
*
* @param request The builder for an authenticated request
* @param authHeader The value of `WWW-Authenticate` header from failed response, if exists
* @param state The authentication state containing token or credential information for the request
*/
protected abstract suspend fun addRequestHeaders(
request: HttpRequestBuilder,
authHeader: HttpAuthHeader?,
state: AuthState,
)
final override suspend fun refreshToken(response: HttpResponse): Boolean {
val newState = tokenHolder.setToken { refreshState(response) }
return newState != null
}
/**
* Refreshes the authentication state if required.
*
* @param response The HTTP response received after a request.
* @return The updated authentication state or `null` if the state couldn't be refreshed.
*/
protected open suspend fun refreshState(response: HttpResponse): AuthState? = fetchState()
/** Clears the currently stored authentication state from the cache. */
public fun clear() {
tokenHolder.clearToken()
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, just noticed that we have a request to make AuthTokenHolder
re-usable:
- KTOR-8126 ktor-client-auth with custom providers
But we also have requests to make it possible to completely disable caching for auth providers:
- KTOR-4759 Auth: BearerAuthProvider caches result of loadToken until process death
- KTOR-6569 Bearer auth: Don't cache client bearer token (option)
- KTOR-4946 Auth: Bearer authentication - unable to update tokens
My proposed CachingAuthProvider
implementation fixes the first issue, but doesn’t address the second one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we have two different use cases:
- A user uses some third-party token storage and wants to delegate responsibility for token lifecycle management to this storage (for example, Firebase SDK). In this case,
AuthProvider
shouldn't cache the token - A user uses their own backend to get a token and (likely) doesn't want extra requests to be made when the token is still valid. For this case caching works fine, but there is a danger of forgetting to clear the token on logout.
Considering these two scenarios, I'd force users to explicitly choose whether they want caching enabled or not. For example, passing a flag to loadToken
:
install(Auth) {
bearer {
loadTokens(cacheEnabled = false) { getFirebaseTokens() }
// Deprecated. Specifies cacheEnabled = true implicitly.
loadTokens { getFirebaseTokens() }
}
}
Alternative solution would be to make it possible to override the implementation of token holder, but it seems a more complex though more flexible solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bjhham, @e5l, @marychatte, what do you think?
For me, v3.1 seems like a good moment to fix all these issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created an issue to rework token storage:
- KTOR-8180 Auth: Provide control over tokens to user code
And marked new methods as @InternalAPI
. For now it is better to keep clearToken
out of the AuthProvider
interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you'd like to add it to the interface, I'd also be happy with that 👍
77cc175
to
0bab8d4
Compare
e6a0810
to
b7e4963
Compare
b7e4963
to
860c05b
Compare
Subsystem
Client, Basic Auth
Motivation
KTOR-7775 Auth: BasicAuthProvider caches credentials until process death
Solution
Add
BasicAuthProvider.clearToken
similarly toBearerAuthProvider
. So now the token can be cleared the following way:Probably, we should come up with a common solution for clearing authorization tokens, but unlikely in 3.1.0.