Hello, What is the difference between the refresh...
# ktor
f
Hello, What is the difference between the refreshToken in loadTokens and refreshTokens block? And is there a sample project where this is use in kotlin multiplatform mobile? With for example public endpoints and enpoints that need authorization
Copy code
install(Auth) {
            bearer {
                loadTokens {
                    BearerTokens(accesToken = "abc123", refreshToken = "xyz111")
                }
                refreshTokens { // this: RefreshTokensParams
                    // Refresh tokens and return them as the 'BearerTokens' instance
                    BearerTokens(accesToken = "def456", refreshToken = "xyz111")
                }
            }
        }
s
What is the difference between the refreshToken in loadTokens and refreshTokens block?
There's no difference. The
loadTokens
returns a token pair loaded from internal storage somewhere. (Or you leave it null if you don't want to use that).
refreshTokens
gives you access to a
client
and
oldTokens
parameters, and requires you to produce a new token pair with that. I've initially had some trouble with it too but after some fiddling it works quite well. I can't give you a sample project but this is a somewhat abridged part of my configuration:
_bearer_ *{*
loadTokens *{* tokenStorage.load() *} // simply load existing tokens if available*
refreshTokens *{*
// use the given client to get new tokens (optional)
val response = client.submitForm(
url = "$_baseUrl_/api/oauth/token",
formParameters = Parameters.build *{*
append("grant_type", "refresh_token")
append("client_id", "my client id")
append("refresh_token", oldTokens!!.refreshToken)
*}*,
encodeInQuery = false,
block = *{*
_accept_(ContentType.Application.Json)
}
)
val content = response.body<OAuthResponse>() // TODO: error handling
val tokens = BearerTokens(accessToken = content.token, refreshToken = content.refresh_token)
tokenStorage.store(tokens) // remember tokens (optional)
tokens
}
}
I've omitted error handling and a few other bits. It works fine on my multiplat setup for Android+Desktop, with the only
tokenStorage
being specialized for each platform. But you could use a library like multiplatform-settings to take care of that.
f
So if loadtokens fails it automatically calls refreshtokens block?
s
AFAIK refreshTokens is only called when a request is made, 401 is returned and ktor determines that it needs to update the credentials. If loadTokens returns null, ktor will simply not be able to attach the token to any request. It will still send the request and get a response (likely a 401).
f
I was kind of confused because loadtokens has also a field refreshtoken. But I guess the refreshTokens block is called when refreshtoken in the loadtokens block fails and you have to re authenticate under the hood
s
oooh do you mean this?
Copy code
loadTokens { 
            refreshTokens { ... } <--
                ...
        }
f
No this
Copy code
loadTokens {
                    BearerTokens(accesToken = "abc123", refreshToken = "xyz111")
                }
you see there is a refreshtoken field. 2nd argument
s
That doesn't belong to loadTokens, but is part of the BearerTokens object. In OAuth, usually you keep the accessToken and refreshToken together. BearerTokens is just a holder object that allows you to pass both of these tokens around between loadTokens and refreshTokens. You can access the latest BearerToken inside refreshTokens (like I do in the sample code), and use the refreshToken from there.
f
Ah ok. Thanks for your help 👍.
🙌 1
I have one more question. How can I differentiate between private and public urls?
I think I got it
Copy code
sendWithoutRequest { request ->
                    //TODO use public or private in your endpoints
                    request.url.encodedPath.contains("login") || request.url.encodedPath.contains("signup")
                }
s
Yep, that's one way. Another would be to use an attribute to "tag" requests that should be authorized:
Copy code
// helper functions
val AuthAttributeKey = AttributeKey<Boolean>("WantToAuthorize")
fun HttpRequestBuilder.authorize() = attributes.put(AuthAttributeKey, true)


// configuration
bearer {
    ...
    sendWithoutRequest { it.attributes.getOrNull(OAuth.AuthAttributeKey) == true }
}

// example of how tell Ktor to add authorization:
client.get<String>("some/url") { authorize() }
🙌 1