https://kotlinlang.org logo
Title
s

Stefan Oltmann

10/18/2021, 12:53 PM
I have trouble understanding how to do OAuth in my client. Following the sample from https://ktor.io/docs/auth.html I can get my user info once, but if I try to reuse the authorizationCode I get an
invalid_grant
I guess I need to save the accessToken, correct? Does someone have a demo how to do proper OAuth with Ktor?
h

Hasan Hosgel (alosdev)

10/18/2021, 1:10 PM
is the oauth flow driven by the client, or the server?
h

Hasan Hosgel (alosdev)

10/18/2021, 1:12 PM
afaik, the client does the login and send the created access token with every call through your api in ktor
s

Stefan Oltmann

10/18/2021, 1:13 PM
why is the auth token invalid after that?
h

Hasan Hosgel (alosdev)

10/18/2021, 1:13 PM
if you need the external ID you create the access token through your backend and save the issuer id from the encoded accesstoken to match your user with the external one
the auth token will be transformed into an accesstoken, which will be used from that moment on.
s

Stefan Oltmann

10/18/2021, 1:15 PM
The Ktor sample is really bad unfortunately. It shows one-time usage. But what I want is auth the user once.
I'm not sure how to do that with Ktor 😕
h

Hasan Hosgel (alosdev)

10/18/2021, 1:15 PM
correct, that is why this is an example of the complete process ;)
s

Stefan Oltmann

10/18/2021, 1:16 PM
A realistic use case would be that I save the access token and provide it next time I build a Ktor client
h

Hasan Hosgel (alosdev)

10/18/2021, 1:17 PM
you don’t save the access token on the server side, the handling always in the client
s

Stefan Oltmann

10/18/2021, 1:19 PM
I have a app that should sync files with Google Drive. My users should login once into the Google account and after that they should stay authenticated
Now the auth token I get is only good for one session.
h

Hasan Hosgel (alosdev)

10/18/2021, 1:20 PM
ok now I understand you, but still the same, only the first call uses the auth token. after that you use the access_token. and needed the refresh_token to generate a new access token

https://a.slack-edge.com/fbd3c/img/api/articles/oauth_scopes_tutorial/slack_oauth_flow_diagram.png

Ktor really lacks samples how to use it in a real world use case 😕
👍 1
h

Hasan Hosgel (alosdev)

10/18/2021, 1:32 PM
what I recommend is to reuse the apiclient, till you get a 403/401 and ask the user to relogin from the beginning. the complete oauth flow is available in the example, nothing to change
and the apiclient can be reused as often you need
s

Stefan Oltmann

10/18/2021, 1:34 PM
If the user closes the app the apiclient is gone
My app is a photo app that should sync with Google Drive in the background. It can be restarted. Users should only login once.
So I need to figure out how apiclient creation looks like for reuse
From the illustration I got the feeling that I need to store the access token and the refresh token in the client database
h

Hasan Hosgel (alosdev)

10/18/2021, 1:36 PM
yes
s

Stefan Oltmann

10/18/2021, 2:27 PM
Wouldn't I loose the automatic refresh token ability that way? 🤔
a

Aleksei Tirman [JB]

10/18/2021, 4:53 PM
Sorry, this answer for the OAuth2 provider on the server. Since access and refresh tokens are stored in memory you can save them in some persistent storage. Here is an example:
fun main() {
    val authClient = HttpClient(CIO)

    val client = HttpClient(CIO) {
        install(Auth) {
            lateinit var tokenInfo: TokenInfo

            bearer {
                loadTokens {
                    var token = getSavedToken()

                    if (token == null) {
                        token = authClient.requestToken()
                        // save token to database
                    }

                    BearerTokens(token.accessToken, token.refreshToken)
                }

                refreshTokens {
                    val info = authClient.refreshToken(tokenInfo.accessToken)
                    // save access token to database
                    BearerTokens(info.accessToken, tokenInfo.refreshToken)
                }
            }
        }
    }
}

data class TokenInfo(val accessToken: String, val refreshToken: String)

suspend fun HttpClient.requestToken(): TokenInfo {
    return TokenInfo("access", "refresh")
}

suspend fun HttpClient.refreshToken(accessToken: String): TokenInfo {
    return TokenInfo("access", "refresh")
}

suspend fun getSavedToken(): TokenInfo? {
    return TokenInfo("access", "refresh")
}
:thank-you: 1
👍 1
s

Stefan Oltmann

10/19/2021, 6:32 AM
Thanks, I'll test it. If it works this should be the official sample ^^