Anders Kirkeby
03/15/2023, 6:10 PMauthorization_code
is returned, and then the token-request is issued witch results in a 200 OK with both the access and refresh token being returned in the body. So everything seems to be good on the strava-side. However, the subsequent callback to /login/authorization-callback
returns a 401 - which I for the life of me cannot understand.
Do any of you have any pointers, or know of any issues with CIO (or something 🤷 )
Code added in the following thread :heart_hands:import com.cndtns.auth.UserSession
import com.cndtns.scrapers.strava.models.SummaryActivity
import com.cndtns.strava.StravaConfig
import com.cndtns.strava.stravaOAuthRoutes
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.engine.*
import io.ktor.server.html.*
import io.ktor.server.request.*
import io.ktor.server.resources.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import kotlinx.html.a
import kotlinx.html.body
import kotlinx.html.p
import kotlinx.serialization.json.Json
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles
val applicationHttpClient = HttpClient(CIO) {
install(Logging) {
level = LogLevel.BODY
} // default logging
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
fun main() {
embeddedServer(io.ktor.server.cio.CIO, port = 8080, module = Application::main).start(wait = true)
}
private fun Application.main(httpClient: HttpClient = applicationHttpClient) {
val redirects = mutableMapOf<String, String>()
install(Resources) // enable @Resource annotations
install(Sessions) {
cookie<UserSession>("user_session")
}
install(Authentication) {
oauth {
urlProvider = { "<http://localhost:8080/login/authorization-callback>" }
providerLookup = {
OAuthServerSettings.OAuth2ServerSettings(
name = "strava",
authorizeUrl = StravaConfig.AUTH_URL,
accessTokenUrl = StravaConfig.TOKEN_URL,
requestMethod = <http://HttpMethod.Post|HttpMethod.Post>,
clientId = StravaConfig.clientId,
clientSecret = StravaConfig.clientSecret,
defaultScopes = listOf("activity:read_all,read_all"),
extraAuthParameters = listOf("approval_prompt" to "auto", "response_type" to "code"),
passParamsInURL = true,
onStateCreated = { call, state ->
val redirectUrl = call.request.queryParameters["redirectUrl"] ?: ""
redirects[state] = redirectUrl
println("Redirect URL for state $state is $redirectUrl")
},
)
}
client = httpClient
}
}
routing {
authenticate {
get("/login/authorization-callback") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
?: throw Exception("No principal was given")
val accessToken = principal.accessToken // TODO: Validate
val state = principal.state ?: throw Exception("No state was given")
call.sessions.set(UserSession(state = state, token = accessToken))
call.respondRedirect("/")
}
get("/login") {
call.respondRedirect("/")
}
}
get("/") {
call.respondHtml {
body {
p {
a("/login") { +"Login with Strava" }
}
}
}
}
get("/{path}") {
val userSession: UserSession? = call.sessions.get()
if (userSession != null) {
val activities: List<SummaryActivity> = httpClient.get("<https://www.strava/api/v3/athlete/activities>") {
headers {
append(HttpHeaders.Authorization, "Bearer ${userSession.token}")
}
}.body()
call.respondText("Here are your activities, ${activities.joinToString(", ") { "${it.id} - ${it.name}" }}!")
} else {
val redirectUrl = URLBuilder("<http://localhost:8080/login>").run {
parameters.append("redirectUrl", call.request.uri)
build()
}
call.respondRedirect(redirectUrl)
}
}
}
}
Aleksei Tirman [JB]
03/16/2023, 6:55 AM/login/authorization-callback
endpoint? If so can you trace what line of your code in that route’s handler causes returning 401?Anders Kirkeby
03/16/2023, 8:15 AM/login/authorization-callback
page but receives a 401 from the ktor-server{
"access_token": "...",
"expires_in": 3599,
"scope": "<https://www.googleapis.com/auth/userinfo.profile>",
"token_type": "Bearer",
"id_token": "..."
}
Strava's
{
"token_type":"Bearer",
"expires_at":1678972747,
"expires_in":14065,
"refresh_token":"...",
"access_token":"...",
"athlete":{}
}
}
Not sure how I can change the expected return type however as this seems omitted in the docs 🤔