OAuth2 & PKCE Flow
MyAnimeList uses the OAuth 2.0 Authorization Code Grant with PKCE (Proof Key for Code Exchange) to authenticate users securely without requiring client secrets on public clients.
For complete details on the underlying OAuth2 endpoints and parameter specifications, see the official MyAnimeList Authorization Reference.
Step 1: Generate Authorization URL & PKCE Verifier
To start authentication, generate a random code verifier (a URL-safe string between 43 and 128 characters) and construct the authorization URL:
import io.ktor.http.URLBuilder
import io.ktor.http.encodeURLQueryComponent
fun buildAuthorizationUrl(
clientId: String,
redirectUri: String,
codeVerifier: String,
state: String? = null
): String {
val builder = URLBuilder("https://myanimelist.net/v1/oauth2/authorize").apply {
parameters.append("response_type", "code")
parameters.append("client_id", clientId)
parameters.append("redirect_uri", redirectUri)
parameters.append("code_challenge", codeVerifier)
parameters.append("code_challenge_method", "plain")
if (state != null) {
parameters.append("state", state)
}
}
return builder.buildString()
}
Step 2: Direct the User to the Authorization URL
Open the authorization URL in a Custom Tab (Android), ASWebAuthenticationSession (iOS), or default web browser (Desktop/JVM):
val codeVerifier = "YOUR_SECURE_RANDOM_CODE_VERIFIER" // e.g. 64-character URL-safe string
val authUrl = buildAuthorizationUrl(
clientId = "YOUR_CLIENT_ID",
redirectUri = "myapp://oauth/callback", // Registered redirect URI in MyAnimeList App Console
codeVerifier = codeVerifier
)
// Open authUrl in browser / Custom Tab
Step 3: Capture the Authorization Code
When the user grants access, MyAnimeList redirects back to your redirect_uri with a code parameter:
myapp://oauth/callback?code=AUTHORIZATION_CODE_HERE&state=STATE_HERE
Extract the code query parameter from the callback URI.
Step 4: Exchange Authorization Code for Tokens
Pass the authorization code and original codeVerifier to client.auth.authenticate:
val session = client.auth.authenticate(
authorizationCode = authorizationCode,
codeVerifier = codeVerifier,
redirectUri = "myapp://oauth/callback" // Must match the redirect URI used in Step 1
)
println("Access Token: ${session.accessToken}")
println("Refresh Token: ${session.refreshToken}")
println("Expires In: ${session.expiresIn} seconds")
client.auth.authenticate automatically saves the access and refresh tokens into the client's configured TokenManager.
Automatic & Manual Token Refresh
Automatic Refresh
Koani handles token expiration transparently. When an API call receives an HTTP 401 Unauthorized status, the internal Ktor client interceptor uses the stored refresh token to obtain a new session and retries the original request automatically.
Manual Refresh
If you need to trigger token refresh explicitly:
val newSession = client.auth.refreshTokens()
Logging Out Users
To clear stored tokens and end the session:
client.auth.logout()
Calling logout() clears tokens from the configured TokenManager, preventing subsequent authenticated requests until the user logs in again.