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