You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi, so I'd like to know if there are any examples of a successful login attempt on Android, any example apps, something I can use to help me do it for my own app.
Here's what I've tried so far but I'm getting stuck at the challenge step.
class TeslaAuthViewModel : ViewModel() {
private lateinit var codeVerifier: String
private lateinit var codeChallenge: String
private val _authState = MutableLiveData<String>()
val authState: LiveData<String> = _authState
init {
generateCodeVerifierAndChallenge()
}
private val retrofit = Retrofit.Builder()
.baseUrl("https://auth.tesla.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
private val teslaAuthApi = retrofit.create(TeslaAuthApi::class.java)
private fun generateCodeVerifierAndChallenge() {
codeVerifier = generateRandomString(86)
codeChallenge = generateCodeChallenge(codeVerifier)
Log.d("TeslaAuthViewModel", "Code Verifier: $codeVerifier")
Log.d("TeslaAuthViewModel", "Code Challenge: $codeChallenge")
}
private fun getAuthUrl(loginHint: String? = null): String {
val authUrl = Uri.parse("https://auth.tesla.com/oauth2/v3/authorize").buildUpon()
.appendQueryParameter("client_id", "ownerapi")
.appendQueryParameter("code_challenge", codeChallenge)
.appendQueryParameter("code_challenge_method", "S256")
.appendQueryParameter("redirect_uri", "https://auth.tesla.com/void/callback")
.appendQueryParameter("response_type", "code")
.appendQueryParameter("scope", "openid email offline_access")
.appendQueryParameter("state", generateRandomString(16))
.apply {
loginHint?.let {
appendQueryParameter("login_hint", it)
}
}
.build()
.toString()
Log.d("TeslaAuthViewModel", "Auth URL: $authUrl")
return authUrl
}
fun handleRedirectUri(uri: Uri) {
Log.d("TeslaAuthViewModel", "Handling Redirect URI: $uri")
val code = uri.getQueryParameter("code")
if (code != null) {
Log.d("TeslaAuthViewModel", "Authorization Code: $code")
exchangeAuthorizationCode(code)
} else {
Log.e("TeslaAuthViewModel", "Authorization failed: No code found in redirect URI")
_authState.postValue("Authorization failed")
}
}
private fun exchangeAuthorizationCode(code: String) {
viewModelScope.launch {
try {
Log.d("TeslaAuthViewModel", "Exchanging Authorization Code for Token")
val response = teslaAuthApi.exchangeCode(
mapOf(
"grant_type" to "authorization_code",
"client_id" to "ownerapi",
"code" to code,
"code_verifier" to codeVerifier,
"redirect_uri" to "https://auth.tesla.com/void/callback"
)
)
if (response.isSuccessful) {
val tokenResponse = response.body()
Log.d("TeslaAuthViewModel", "Token Response: $tokenResponse")
_authState.postValue(tokenResponse?.accessToken ?: "Authorization failed")
} else {
Log.e("TeslaAuthViewModel", "Authorization failed: ${response.errorBody()?.string()}")
_authState.postValue("Authorization failed")
}
} catch (e: Exception) {
Log.e("TeslaAuthViewModel", "Authorization failed with exception", e)
_authState.postValue("Authorization failed")
}
}
}
private fun generateRandomString(length: Int): String {
val allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return (1..length)
.map { allowedChars.random() }
.joinToString("")
}
@SuppressLint("NewApi")
private fun generateCodeChallenge(verifier: String): String {
val bytes = verifier.toByteArray(StandardCharsets.US_ASCII)
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(bytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(digest)
}
private suspend fun extractHiddenFields(html: String): Map<String, String> {
val document = Jsoup.parse(html)
val hiddenFields = mutableMapOf<String, String>()
document.select("input[type=hidden]").forEach { element ->
hiddenFields[element.attr("name")] = element.attr("value")
}
Log.d("TeslaAuthViewModel", "Hidden Fields: $hiddenFields")
return hiddenFields
}
private suspend fun checkForChallenge(html: String): Boolean {
return html.contains("sec_chlge_form")
}
private suspend fun submitChallengeForm(html: String, cookie: String) {
val document = Jsoup.parse(html)
val challengeForm = document.select("form#chlge").first()
val action = challengeForm?.attr("action") ?: ""
val hiddenFields = mutableMapOf<String, String>()
challengeForm?.select("input[type=hidden]")?.forEach { element ->
hiddenFields[element.attr("name")] = element.attr("value")
}
val challengeUrl = "https://auth.tesla.com$action"
Log.d("TeslaAuthViewModel", "Submitting Challenge Form: $hiddenFields")
val response = teslaAuthApi.submitLoginForm(challengeUrl, hiddenFields, cookie)
if (response.isSuccessful) {
val locationHeader = response.headers()["location"]
Log.d("TeslaAuthViewModel", "Location Header: $locationHeader")
if (locationHeader != null) {
val uri = Uri.parse(locationHeader)
handleRedirectUri(uri)
} else {
Log.e("TeslaAuthViewModel", "Authorization failed: Location header is null")
_authState.postValue("Authorization failed")
}
} else {
Log.e("TeslaAuthViewModel", "Challenge Form Submission Response Body: ${response.body()?.string()}")
_authState.postValue("Authorization failed")
}
}
suspend fun submitLoginForm(
url: String,
hiddenFields: Map<String, String>,
email: String,
password: String,
cookie: String
): Response<ResponseBody> {
val formBody = hiddenFields.toMutableMap().apply {
put("identity", email)
put("credential", password)
}
Log.d("TeslaAuthViewModel", "Submitting Login Form: $formBody")
return teslaAuthApi.submitLoginForm(url, formBody, cookie)
}
fun performLogin(email: String, password: String) {
viewModelScope.launch {
try {
val authUrl = getAuthUrl(email)
val initialResponse = teslaAuthApi.getLoginPage(authUrl)
if (initialResponse.isSuccessful) {
val html = initialResponse.body()?.string() ?: ""
Log.d("TeslaAuthViewModel", "Initial Login Page Response Body: $html")
val hiddenFields = extractHiddenFields(html)
val cookie = initialResponse.headers()["set-cookie"] ?: ""
Log.d("TeslaAuthViewModel", "Initial Cookie: $cookie")
val submitResponse = submitLoginForm(authUrl, hiddenFields, email, password, cookie)
if (submitResponse.isSuccessful) {
val responseBody = submitResponse.body()?.string() ?: ""
Log.d("TeslaAuthViewModel", "Form Submission Response Body: $responseBody")
if (checkForChallenge(responseBody)) {
submitChallengeForm(responseBody, cookie)
} else {
val locationHeader = submitResponse.headers()["location"]
Log.d("TeslaAuthViewModel", "Location Header: $locationHeader")
if (locationHeader != null) {
val uri = Uri.parse(locationHeader)
handleRedirectUri(uri)
} else {
Log.e("TeslaAuthViewModel", "Authorization failed: Location header is null")
_authState.postValue("Authorization failed")
}
}
} else {
Log.e("TeslaAuthViewModel", "Form Submission Response Body: ${submitResponse.body()?.string()}")
_authState.postValue("Authorization failed")
}
} else {
Log.e("TeslaAuthViewModel", "Initial Login Page Request failed: ${initialResponse.errorBody()?.string()}")
_authState.postValue("Authorization failed")
}
} catch (e: Exception) {
Log.e("TeslaAuthViewModel", "Authorization failed with exception", e)
_authState.postValue("Authorization failed")
}
}
}
}
Here's the logs that are generated when this is run and sign in with valid credentials are provided:
This discussion was converted from issue #781 on July 06, 2024 00:34.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hi, so I'd like to know if there are any examples of a successful login attempt on Android, any example apps, something I can use to help me do it for my own app.
Here's what I've tried so far but I'm getting stuck at the challenge step.
Here's the logs that are generated when this is run and sign in with valid credentials are provided:
Beta Was this translation helpful? Give feedback.
All reactions