Kotlin LoginMaker to a Laravel project

Hi all,
I’m developing a class to perform login to a mine Laravel site (v 11 with Inertia). It work for a 70% but when it POST login request I receive a HTTP 419 error. This is my code that if you allow me to adjust, may be usefull for many people:

package com.example.mysecrets

import android.util.Log
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.*

class LoginMaker(
    private val host: String,
    private val username: String,
    private val password: String
) {
    private val TAG = "My:LoginMaker"
    private var xsrfToken: String = ""
    private var siteSession: String = ""
    private var sessionName: String = "mysite_session"
    private var inertiaVersion: String = "xxxxxxxxxxxxxxxxxxxxxxxx"
    private var connection: HttpURLConnection? = null  // Connessione globale

    // Funzione principale per eseguire il login
    suspend fun doLogin(): Pair<String, String>? {
        Log.d(TAG, "doLogin:16: Inizio sequenza di login per l'host $host...")

        // Esegui fase1, fase2, fase3 in sequenza
        if (fase1() && fase2() && fase3()) {
            Log.d(TAG, "doLogin:27: Login completato con successo.")
            return Pair(xsrfToken, siteSession)
        } else {
            Log.e(TAG, "doLogin:30: Login fallito durante una delle fasi.")
            return null
        }
    }



    // Fase 1
    private suspend fun fase1(): Boolean {
        Log.d(TAG, "fase1:33: Esecuzione richiesta GET per inizializzare i cookie...")
        return withContext(Dispatchers.IO) {
            try {
                val url = URL(host)
                connection = url.openConnection() as HttpURLConnection
                connection?.apply {
                    // Imposta le proprietà della connessione prima di aprirla
                    setRequestProperty("XSRF-TOKEN", xsrfToken)
                    setRequestProperty("Cookie", "$sessionName=$siteSession")  // Se necessario aggiungi altre proprietà
                    connectTimeout = 5000
                    readTimeout = 5000
                }

                // Ora che la connessione è configurata, possiamo aprirla
                val responseCode = connection?.responseCode
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    filtraHeader("fase1")?.let {
                        xsrfToken = it.first
                        siteSession = it.second
                        Log.d(TAG, "fase1:87: Fase 1 completata con successo.")
                        return@withContext true
                    }
                }

                return@withContext false
            } catch (e: Exception) {
                Log.e(TAG, "fase1:catch: Errore nella fase 1: ${e.message}")
                return@withContext false
            }
        }
    }


    private suspend fun fase2(): Boolean {
        Log.d(TAG, "fase2:56: Fase 2: Richiesta GET all'endpoint /login...")
        return withContext(Dispatchers.IO) {
            try {
                val url = URL(host)
                // Imposta le proprietà della connessione prima di stabilire la connessione
                connection = url.openConnection() as HttpURLConnection
                connection?.apply {
                    setRequestProperty("Referer", "$host/")                // Imposta il Referer
                    setRequestProperty("X-Inertia-Version", inertiaVersion)  // Imposta la versione Inertia
                    connectTimeout = 5000  // Timeout di connessione
                    readTimeout = 5000     // Timeout di lettura
                }

                // A questo punto, apri la connessione e ottieni la risposta
                val responseCode = connection?.responseCode  // La connessione ora viene aperta
                val responseMessage = connection?.inputStream?.bufferedReader()?.use { it.readText() }

                if (responseCode == HttpURLConnection.HTTP_OK) {
                    Log.d(TAG, "fase2:133: Estrazione cookie")
                    filtraHeader("fase2")?.let {
                        xsrfToken = it.first  // Assegna il nuovo XSRF token
                        siteSession = it.second  // Assegna la sessione
                        Log.d(TAG, "fase2:113: Fase 2 completata con successo.")
                        return@withContext true
                    }
                } else {
                    Log.e(TAG, "fase2:139: Fase 2 fallita: Risposta HTTP $responseCode.")
                    Log.e(TAG, "fase2:140: Dettagli risposta: $responseMessage")
                }
                return@withContext false
            } catch (e: Exception) {
                Log.e(TAG, "fase2:144: Errore nella fase 2: ${e.message}")
                return@withContext false
            }
        }
    }

    private suspend fun fase3(): Boolean {
        Log.d(TAG, "fase3:153: Fase 3: Richiesta POST a /login con i dati di autenticazione...")
        return withContext(Dispatchers.IO) {
            try {
                val url = URL("$host/login") // Assicurati che l'URL sia corretto per la tua richiesta
                connection = url.openConnection() as HttpURLConnection
                connection?.apply {
                    requestMethod = "POST"
                    setRequestProperty("Cookie", "XSRF-TOKEN=$xsrfToken; $sessionName=$siteSession")
                    setRequestProperty("X-XSRF-TOKEN", xsrfToken)
                    setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
                    setRequestProperty("Accept", "application/json")
                    doOutput = true
                    connectTimeout = 10000  // Timeout per la connessione
                    readTimeout = 10000     // Timeout per la lettura
                }

                // Log of all request headers before sending the request
                connection?.requestProperties?.forEach { (key, value) ->
                    Log.d(TAG, "Request Header: $key: $value")
                }
                Log.d(TAG, "Request Header: Cookie: XSRF-TOKEN=$xsrfToken")
                Log.d(TAG, "Request Header:         $sessionName=$siteSession")

                // Corpo della richiesta FormData con il campo _token CSRF
                val body = "username=$username&password=$password&remember=false&_token=$xsrfToken"
                Log.d(TAG, "fase3: Request Body: $body")

                // Scrivi i dati nel body della richiesta
                connection?.outputStream?.use { outputStream ->
                    OutputStreamWriter(outputStream).apply {
                        write(body)
                        flush()
                    }
                }

                // Log della risposta
                val responseCode = connection?.responseCode
                Log.d(TAG, "fase3: Response Code: $responseCode")

                val responseStream = if (responseCode in 200..299) {
                    connection?.inputStream
                } else {
                    connection?.errorStream
                }

                responseStream?.bufferedReader()?.use { reader ->
                    val responseMessage = reader.readText()
                    Log.d(TAG, "fase3: Response Message: $responseMessage")
                }

                // Verifica il codice di risposta
                if (responseCode == 419) {
                    Log.e(TAG, "fase3:107: Risposta non gestita. Codice 419 - CSRF token non valido o sessione scaduta.")
                    return@withContext false
                }

                if (responseCode == HttpURLConnection.HTTP_OK) {
                    // Se la risposta è positiva, estrae i nuovi valori del token
                    filtraHeader("fase3")?.let {
                        xsrfToken = it.first
                        siteSession = it.second
                        Log.d(TAG, "fase3:103: Fase 3 completata con successo.")
                        return@withContext true
                    }
                } else {
                    Log.e(TAG, "fase3:107: Fase 3 fallita: Risposta HTTP $responseCode.")
                }

                return@withContext false
            } catch (e: Exception) {
                Log.e(TAG, "fase3:110: Errore nella fase 3: ${e.message}")
                return@withContext false
            }
        }
    }



    // Funzione di logout
    suspend fun logout(): Boolean {
        Log.d(TAG, "logout:53: Inizio processo di logout...")
        if (xsrfToken.isEmpty() || siteSession.isEmpty()) {
            Log.e(TAG, "logout:56: Errore: non è stato eseguito il login o i cookie non sono stati settati.")
            return false
        }

        return withContext(Dispatchers.IO) {
            try {
                connection?.apply {
                    requestMethod = "POST"
                    setRequestProperty("X-XSRF-TOKEN", xsrfToken)
                    setRequestProperty("Cookie", "$sessionName=$siteSession; XSRF-TOKEN=$xsrfToken")
                    doOutput = true
                }

                val outputStream = connection?.outputStream
                outputStream?.write("2".toByteArray()) // Corpo della richiesta per logout
                outputStream?.flush()

                val responseCode = connection?.responseCode
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    Log.d(TAG, "logout:72: Logout eseguito con successo.")
                    xsrfToken = ""
                    siteSession = ""
                    return@withContext true
                } else {
                    Log.e(TAG, "logout:76: Errore durante il logout. Risposta HTTP: $responseCode")
                    return@withContext false
                }
            } catch (e: Exception) {
                Log.e(TAG, "logout:80: Errore nel processo di logout: ${e.message}")
                return@withContext false
            } finally {
                connection?.disconnect()  // Chiudiamo la connessione al termine
            }
        }
    }

    // Funzione per estrarre i cookie
    private fun filtraHeader(fase:String): Pair<String, String>? {
        val cookies = connection?.headerFields?.get("Set-Cookie")
        var xsrf: String? = null
        var session: String? = ""

        connection?.headerFields?.get("X-Inertia-Version")
            ?.firstOrNull() // Ottiene il primo valore della lista, o null se la lista è vuota
            ?.takeIf { it != inertiaVersion } // Confronta con inertiaVersion solo se esiste
            ?.let { inertiaVersion = it } // Aggiorna inertiaVersion se il valore è diverso


        if (cookies != null) {
            Log.d(TAG, "fase1:55: i cookie sono presenti")
            var xsrfFound = false
            var sessionFound = false
            for (cookie in cookies) {
                if (cookie.contains("XSRF-TOKEN")) {
                    xsrf = cookie.split(";")[0].split("=")[1]

                    if(xsrfToken == xsrf) Log.d(TAG, "filtraHeader:$fase: cookie xrsf non modificato: $xsrf")
                    else Log.d(TAG, "filtraHeader:$fase: cookie xrsf MODIFICATO: $xsrf")
                    xsrfToken = xsrf
                    xsrfFound = true
                }
                // Trova il nome del cookie di sessione
                if (cookie.contains("session")) {
                    val sessionParts = cookie.split(";")[0].split("=")
                    sessionName = sessionParts[0] // Nome del cookie
                    session = sessionParts[1] // Valore del cookie
                    if(session == siteSession) Log.d(TAG, "filtraHeader:$fase: session non modificato: $sessionName: $session")
                    else Log.d(TAG, "filtraHeader:$fase: session MODIFICATO: $sessionName: $session")
                    siteSession = session
                    sessionFound = true
                    /*if(xsrfToken != siteSession) {
                        siteSession = xsrfToken
                            Log.d(TAG, "filtraHeader:$fase: FORZATURA :xsrfToken = siteSession")
                    }*/
                }
            }
            // Dopo il ciclo, se i cookie non sono stati trovati, assegna un valore predefinito
            if (!xsrfFound) {
                Log.d(TAG, "filtraHeader:$fase: cookie xrsf non presente")
                xsrfToken = "xx" // Solo se non trovato
            }
            if (!sessionFound) {
                Log.d(TAG, "filtraHeader:$fase: cookie di sessione assente")
                siteSession = "xx" // Solo se non trovato
            }

        }
        return if (!xsrf.isNullOrEmpty() || !session.isNullOrEmpty()) {
            Pair(xsrfToken, siteSession)
        } else {
            Pair(xsrfToken, siteSession)
        }
    }
}


Thanks, bye