Pular para o conteúdo principal

Adicionar autenticação ao seu aplicativo Android (Kotlin/Java)

Este guia mostrará como integrar o Logto ao seu aplicativo Android.

dica:

Pré-requisitos

Instalação

nota:

O nível mínimo de API do Android suportado pelo Logto Android SDK é o nível 24.

O Logto Android SDK está disponível em duas versões principais:

  • v3: Abre a experiência de login em Chrome Custom Tabs (o navegador do sistema), o que permite o login com passkey e compartilha a sessão do navegador. Observe que a v3 remove o suporte para os conectores WeChat (Nativo) e Alipay (Nativo); você pode usar WeChat (Web) e Alipay (Web) em vez disso, que funcionam através do navegador. Se você depende dos conectores nativos, permaneça na v2.
  • v2: Abre a experiência de login em um WebView incorporado, o que é necessário para os conectores sociais nativos, mas não suporta login com passkey (WebView não suporta WebAuthn, o padrão subjacente das passkeys).

Este guia cobre ambas as versões. Escolha sua versão nas abas abaixo, e a escolha será mantida sincronizada ao longo deste guia.

Antes de instalar o Logto Android SDK, certifique-se de que mavenCentral() está adicionado à configuração do seu repositório no arquivo de build do projeto Gradle:

settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}

Adicione o Logto Android SDK às suas dependências:

Use a versão mais recente da v3 como versão:

build.gradle.kts
dependencies {
implementation("io.logto.sdk:android:3.0.0")
}

Como o SDK precisa de acesso à internet, você precisa adicionar a seguinte permissão ao seu arquivo AndroidManifest.xml:

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- adicionar permissão de internet -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- outras configurações... -->
</manifest>

Integração

Inicializar LogtoClient

Crie um LogtoViewModel.kt e inicie o LogtoClient neste view model:

LogtoViewModel.kt
//...com outros imports
import io.logto.sdk.android.LogtoClient
import io.logto.sdk.android.type.LogtoConfig

class LogtoViewModel(application: Application) : AndroidViewModel(application) {
private val logtoConfig = LogtoConfig(
endpoint = "<your-logto-endpoint>",
appId = "<your-app-id>",
scopes = null,
resources = null,
usingPersistStorage = true,
)

private val logtoClient = LogtoClient(logtoConfig, application)

companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// Obtenha o objeto Application dos extras
val application = checkNotNull(extras[APPLICATION_KEY])
return LogtoViewModel(application) as T
}
}
}
}

em seguida, crie um LogtoViewModel para o seu MainActivity.kt:

MainActivity.kt
//...com outros imports
class MainActivity : AppCompatActivity() {
private val logtoViewModel: LogtoViewModel by viewModels { LogtoViewModel.Factory }
//...outros códigos
}

Configurar URI de redirecionamento

Antes de entrarmos nos detalhes, aqui está uma visão geral rápida da experiência do usuário final. O processo de login pode ser simplificado da seguinte forma:

  1. Seu app invoca o método de login.
  2. O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
  3. O usuário faz login e é redirecionado de volta para seu app (configurado como o URI de redirecionamento).

Sobre o login baseado em redirecionamento

  1. Este processo de autenticação segue o protocolo OpenID Connect (OIDC), e o Logto aplica medidas de segurança rigorosas para proteger o login do usuário.
  2. Se você tiver vários aplicativos, pode usar o mesmo provedor de identidade (Logto). Uma vez que o usuário faz login em um aplicativo, o Logto completará automaticamente o processo de login quando o usuário acessar outro aplicativo.

Para saber mais sobre a lógica e os benefícios do login baseado em redirecionamento, veja Experiência de login do Logto explicada.


Vamos mudar para a página de detalhes do Aplicativo no Logto Console. Adicione um URI de redirecionamento io.logto.android://io.logto.sample/callback e clique em "Salvar alterações".

URI de redirecionamento no Logto Console

No Android, o URI de redirecionamento segue o padrão: $(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback:

  • O LOGTO_REDIRECT_SCHEME deve ser um esquema personalizado no formato de domínio reverso.
  • O YOUR_APP_PACKAGE é o nome do pacote do seu aplicativo.

Assumindo que você use io.logto.android como o esquema personalizado LOGTO_REDIRECT_SCHEME, e io.logto.sample como o nome do pacote do seu aplicativo, o URI de redirecionamento deve ser io.logto.android://io.logto.sample/callback.

Na v3, a experiência de login é aberta em uma Custom Tab (o navegador do sistema), e o redirecionamento é roteado de volta ao seu aplicativo por meio de um filtro de intent em nível de sistema operacional. Você precisa declarar o esquema do seu URI de redirecionamento com o placeholder de manifesto logtoRedirectScheme no arquivo de build do seu aplicativo:

build.gradle.kts
android {
defaultConfig {
manifestPlaceholders["logtoRedirectScheme"] = "io.logto.android"
}
}

Além disso, a v3 reforça o padrão do URI de redirecionamento por meio da correspondência do filtro de intent do Android, então um URI de redirecionamento que desvie do padrão nunca será entregue ao seu aplicativo:

  • O esquema deve ser igual ao placeholder de manifesto logtoRedirectScheme.
  • O host deve ser seu applicationId.
  • O caminho deve ser /callback.

Mantenha o esquema e o host em minúsculas, pois a correspondência do filtro de intent diferencia maiúsculas de minúsculas e os navegadores convertem o esquema para minúsculas.

Para usar Android App Links (um URI de redirecionamento https em um domínio que você possui) em vez do esquema personalizado:

  1. Hospede o arquivo Digital Asset Links em https://your.domain/.well-known/assetlinks.json, declarando seu application ID e as impressões digitais SHA-256 dos seus certificados de assinatura. Ao publicar com Play App Signing, você pode encontrar a impressão digital de release no Play Console em Configuração > Assinatura do app. O arquivo deve ser servido como Content-Type: application/json com HTTP 200 e sem redirecionamentos.

  2. Declare o intent filter de App Links na activity de recebimento de redirecionamento do SDK io.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivity no seu arquivo AndroidManifest.xml. Se você não usar o esquema personalizado, remova o filtro padrão do SDK com tools:node="removeAll", e o placeholder de manifesto logtoRedirectScheme não será mais necessário:

    AndroidManifest.xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application>
    <activity android:name="io.logto.sdk.android.auth.logto.LogtoRedirectReceiverActivity">
    <!-- Omitir esta linha para manter o redirecionamento por esquema personalizado funcionando em paralelo. -->
    <intent-filter tools:node="removeAll" />
    <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="your.domain" android:path="/callback" />
    </intent-filter>
    </activity>
    </application>
    </manifest>
  3. Adicione https://your.domain/callback como um URI de redirecionamento (e, se for usado para logout, um URI de redirecionamento pós-logout) na página de detalhes do aplicativo no Logto Console, e passe-o para signIn / signOut.

Lembre-se de que o callback agora é uma URL real no seu domínio, então sirva uma página de fallback lá (por exemplo, um botão "Retornar ao app") para navegadores que não abrem App Links em um redirecionamento do servidor. O botão só precisa apontar para a URL atual (por exemplo, definindo href para window.location.href): os parâmetros de autorização estão na query string, e um clique iniciado pelo usuário dá à mesma URL outra chance de ser roteada para o seu app. No Android 12+ um domínio não verificado nunca abre o app, então um assetlinks.json quebrado falha silenciosamente. Você pode verificar o estado da verificação com adb shell pm get-app-links <applicationId>.

Implementar login e logout

nota:

Antes de chamar logtoClient.signIn, certifique-se de que você configurou corretamente o URI de redirecionamento no Admin Console.

Você pode usar logtoClient.signIn para autenticar o usuário e logtoClient.signOut para desconectar o usuário.

Na v3, logtoClient.signOut realiza uma saída completa: limpa as credenciais locais, revoga o token de atualização (refresh token) e encerra a sessão Logto abrindo o endpoint de encerramento de sessão no navegador. O navegador então retorna ao seu app através do post sign-out redirect URI. Antes de usar, acesse a página de detalhes do aplicativo no Logto Console, adicione o post sign-out redirect URI io.logto.android://io.logto.sample/callback e clique em "Salvar alterações". O post sign-out redirect URI segue o mesmo padrão do redirect URI, e seu esquema também deve corresponder ao placeholder de manifesto logtoRedirectScheme.

Por exemplo, em um app Android:

LogtoModelView.kt
//...com outros imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...outros códigos

// Adicione um live data para observar o status de autenticação
private val _authenticated = MutableLiveData(logtoClient.isAuthenticated)
val authenticated: LiveData<Boolean>
get() = _authenticated

fun signIn(context: Activity) {
logtoClient.signIn(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// Atualize o live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}

fun signOut(context: Activity) {
logtoClient.signOut(context, "io.logto.android://io.logto.sample/callback") { logtoException ->
logtoException?.let { println(it) }
// Atualize o live data
_authenticated.postValue(logtoClient.isAuthenticated)
}
}
}

Depois, chame os métodos signIn e signOut em sua activity:

MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...outros códigos

// Suponha que você tenha um botão com id "sign_in_button" no seu layout
val signInButton = findViewById<Button>(R.id.sign_in_button)
signInButton.setOnClickListener {
logtoViewModel.signIn(this)
}

// Suponha que você tenha um botão com id "sign_out_button" no seu layout
val signOutButton = findViewById<Button>(R.id.sign_out_button)
signOutButton.setOnClickListener {
if (logtoViewModel.authenticated) { // Verifique se o usuário está autenticado
logtoViewModel.signOut(this)
}
}

// Observe o status de autenticação para atualizar a UI
logtoViewModel.authenticated.observe(this) { authenticated ->
if (authenticated) {
// O usuário está autenticado
signInButton.visibility = View.GONE
signOutButton.visibility = View.VISIBLE
} else {
// O usuário não está autenticado
signInButton.visibility = View.VISIBLE
signOutButton.visibility = View.GONE
}
}

}
}
nota:
  • Você também pode chamar logtoClient.signOut(context) sem um post sign-out redirect URI. Nenhuma configuração no Console é necessária nesse caso: o navegador mostra a página de saída do Logto, e o usuário retorna ao app fechando-a manualmente.
  • Se nenhum contexto de UI estiver disponível, você pode chamar logtoClient.clearCredentials para limpar as credenciais locais e revogar o token de atualização (refresh token). Observe que isso mantém a sessão Logto no navegador, então o próximo signIn pode autenticar o usuário silenciosamente através dessa sessão.

Ponto de verificação: Teste seu aplicativo

Agora, você pode testar seu aplicativo:

  1. Execute seu aplicativo, você verá o botão de login.
  2. Clique no botão de login, o SDK iniciará o processo de login e redirecionará você para a página de login do Logto.
  3. Após fazer login, você será redirecionado de volta para seu aplicativo e verá o botão de logout.
  4. Clique no botão de logout para limpar o armazenamento de tokens e sair.

Obter informações do usuário

Exibir informações do usuário

Para exibir as informações do usuário, você pode usar o método logtoClient.getIdTokenClaims(). Por exemplo, você pode obter as informações do usuário em um ViewModel e depois exibi-las em sua activity:

LogtoModelView.kt
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...outros códigos

// Adicione um live data para observar as reivindicações do id token
private val _idTokenClaims = MutableLiveData<IdTokenClaims>()
val idTokenClaims: LiveData<IdTokenClaims>
get() = _idTokenClaims

fun getIdTokenClaims() {
logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
logtoException?.let { _logtoException.postValue(it) } ?: _idTokenClaims.postValue(idTokenClaims)
}
}
}
MainActivity.kt
//...com outros imports
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//...outros códigos

// Suponha que você tenha um TextView com id `user_info_text_view` no seu layout
val userInfoResponseTextView: TextView = findViewById(R.id.user_info_text_view)
logtoViewModel.userInfoResponse.observe(this) { userInfoResponse ->
userInfoResponseTextView.text = if (userInfoResponse !== null) {
val json = Gson().toJson(userInfoResponse, UserInfoResponse::class.java)
JSONObject(json).toString(2)
} else {
""
}
}
}
}

Solicitar reivindicações adicionais

Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de logtoClient.getIdTokenClaims(). Isso ocorre porque OAuth 2.0 e OpenID Connect (OIDC) são projetados para seguir o princípio do menor privilégio (PoLP), e o Logto é construído com base nesses padrões.

Por padrão, reivindicações limitadas são retornadas. Se você precisar de mais informações, pode solicitar escopos adicionais para acessar mais reivindicações.

info:

Uma "reivindicação (Claim)" é uma afirmação feita sobre um sujeito; um "escopo (Scope)" é um grupo de reivindicações. No caso atual, uma reivindicação é uma informação sobre o usuário.

Aqui está um exemplo não normativo da relação escopo - reivindicação:

dica:

A reivindicação "sub" significa "sujeito (Subject)", que é o identificador único do usuário (ou seja, ID do usuário).

O Logto SDK sempre solicitará três escopos: openid, profile e offline_access.

Para solicitar escopos adicionais, você pode passá-los para o objeto LogtoConfig. Por exemplo:

LogtoViewModel.kt
private val logtoConfig = LogtoConfig(
// ...outras configs
scopes = listOf("email", "phone"), // ou `listOf(UserScope.EMAIL, UserScope.PHONE)`
)

Então você pode acessar as reivindicações adicionais no valor retornado de logtoClient.getIdTokenClaims():

logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
println("IdTokenClaims:$idTokenClaims")
}
// Agora você pode acessar as reivindicações adicionais `claims.email`, `claims.phone`, etc.

Reivindicações que precisam de solicitações de rede

Para evitar o inchaço do Token de ID (ID token), algumas reivindicações requerem solicitações de rede para serem buscadas. Por exemplo, a reivindicação custom_data não está incluída no objeto do usuário, mesmo que seja solicitada nos escopos. Para acessar essas reivindicações, você pode usar o método logtoClient.fetchUserInfo():

LogtoViewModel.kt
logtoClient.fetchUserInfo {_, userInfoResponse ->
println("UserInfoResponse:$userInfoResponse")
}
// Agora você pode acessar a reivindicação `userInfo.custom_data`
Este método buscará as informações do usuário solicitando ao endpoint userinfo. Para saber mais sobre os escopos e reivindicações disponíveis, veja a seção Escopos e reivindicações.

Escopos e reivindicações

Logto utiliza as convenções de escopos e reivindicações (scopes and claims) do OIDC para definir os escopos e reivindicações para obtenção de informações do usuário a partir do token de ID (ID token) e do endpoint userinfo do OIDC. Tanto "escopo (Scope)" quanto "reivindicação (Claim)" são termos das especificações do OAuth 2.0 e OpenID Connect (OIDC).

Para reivindicações padrão do OIDC, a inclusão no token de ID é estritamente determinada pelos escopos solicitados. Reivindicações estendidas (como custom_data e organizations) podem ser configuradas adicionalmente para aparecer no token de ID através das configurações de Token de ID personalizado.

Aqui está a lista de escopos suportados e as reivindicações correspondentes:

Escopos OIDC padrão

openid (padrão)

Nome da reivindicaçãoTipoDescrição
substringO identificador único do usuário

profile (padrão)

Nome da reivindicaçãoTipoDescrição
namestringO nome completo do usuário
usernamestringO nome de usuário do usuário
picturestringURL da foto de perfil do usuário final. Esta URL DEVE se referir a um arquivo de imagem (por exemplo, um arquivo de imagem PNG, JPEG ou GIF), em vez de uma página da Web contendo uma imagem. Observe que esta URL DEVE referenciar especificamente uma foto de perfil do usuário final adequada para exibição, em vez de uma foto arbitrária tirada pelo usuário final.
created_atnumberMomento em que o usuário final foi criado. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z).
updated_atnumberMomento em que as informações do usuário final foram atualizadas pela última vez. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z).

Outras reivindicações padrão incluem family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo e locale também serão incluídas no escopo profile sem a necessidade de solicitar o endpoint userinfo. Uma diferença em relação às reivindicações acima é que essas reivindicações só serão retornadas quando seus valores não forem vazios, enquanto as reivindicações acima retornarão null se os valores estiverem vazios.

nota:

Diferente das reivindicações padrão, as reivindicações created_at e updated_at usam milissegundos em vez de segundos.

email

Nome da reivindicaçãoTipoDescrição
emailstringO endereço de email do usuário
email_verifiedbooleanSe o endereço de email foi verificado

phone

Nome da reivindicaçãoTipoDescrição
phone_numberstringO número de telefone do usuário
phone_number_verifiedbooleanSe o número de telefone foi verificado

address

Consulte o OpenID Connect Core 1.0 para detalhes sobre a reivindicação de endereço.

info:

Escopos marcados como (padrão) são sempre solicitados pelo SDK do Logto. As reivindicações sob escopos OIDC padrão são sempre incluídas no token de ID quando o escopo correspondente é solicitado — elas não podem ser desativadas.

Escopos estendidos

Os seguintes escopos são estendidos pelo Logto e retornarão reivindicações através do endpoint userinfo. Essas reivindicações também podem ser configuradas para serem incluídas diretamente no token de ID através de Console > Custom JWT. Veja Token de ID personalizado para mais detalhes.

custom_data

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
custom_dataobjectOs dados personalizados do usuário

identities

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
identitiesobjectAs identidades vinculadas do usuário
sso_identitiesarrayAs identidades SSO vinculadas do usuário

roles

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
rolesstring[]Os papéis do usuário

urn:logto:scope:organizations

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
organizationsstring[]Os IDs das organizações às quais o usuário pertence
organization_dataobject[]Os dados das organizações às quais o usuário pertence
nota:

Essas reivindicações de organização também podem ser recuperadas via endpoint userinfo ao usar um token opaco. No entanto, tokens opacos não podem ser usados como tokens de organização para acessar recursos específicos da organização. Veja Token opaco e organizações para mais detalhes.

urn:logto:scope:organization_roles

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
organization_rolesstring[]Os papéis da organização aos quais o usuário pertence no formato <organization_id>:<role_name>

Recursos de API e organizações

Recomendamos ler 🔐 Controle de Acesso Baseado em Papel (RBAC) primeiro para entender os conceitos básicos do RBAC do Logto e como configurar corretamente os recursos de API.

Configurar cliente Logto

Depois de configurar os recursos de API, você pode adicioná-los ao configurar o Logto em seu aplicativo:

LogtoViewModel.kt
val logtoConfig = LogtoConfig(
//...other configs
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"), // Adicionar recursos de API
)

Cada recurso de API tem suas próprias permissões (escopos).

Por exemplo, o recurso https://shopping.your-app.com/api tem as permissões shopping:read e shopping:write, e o recurso https://store.your-app.com/api tem as permissões store:read e store:write.

Para solicitar essas permissões, você pode adicioná-las ao configurar o Logto em seu aplicativo:

LogtoViewModel.kt
val logtoConfig = LogtoConfig(
// ..other configs
scopes = listOf("shopping:read", "shopping:write", "store:read", "store:write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)

Você pode notar que os escopos são definidos separadamente dos recursos de API. Isso ocorre porque Resource Indicators for OAuth 2.0 especifica que os escopos finais para a solicitação serão o produto cartesiano de todos os escopos em todos os serviços de destino.

Assim, no caso acima, os escopos podem ser simplificados a partir da definição no Logto, ambos os recursos de API podem ter escopos read e write sem o prefixo. Então, na configuração do Logto:

LogtoViewModel.kt
val logtoConfig = LogtoConfig(
// ...other configs
scopes = listOf("read", "write"),
resources = listOf("https://shopping.your-app.com/api", "https://store.your-app.com/api"),
)

Para cada recurso de API, ele solicitará os escopos read e write.

nota:

Não há problema em solicitar escopos que não estão definidos nos recursos de API. Por exemplo, você pode solicitar o escopo email mesmo que os recursos de API não tenham o escopo email disponível. Escopos indisponíveis serão ignorados com segurança.

Após o login bem-sucedido, o Logto emitirá os escopos apropriados para os recursos de API de acordo com os papéis do usuário.

Buscar token de acesso para o recurso de API

Para buscar o token de acesso para um recurso de API específico, você pode usar o método getAccessToken:

LogtoViewModel.kt
logtoClient.getAccessToken("https://shopping.your-app.com/api") { logtoException, accessToken ->
logtoException?.let { println(it) }
accessToken?.let { println(it) }
}

Este método retornará um token de acesso JWT que pode ser usado para acessar o recurso de API quando o usuário tiver as permissões relacionadas. Se o token de acesso em cache atual tiver expirado, este método tentará automaticamente usar um token de atualização para obter um novo token de acesso.

Buscar tokens de organização

Se organização é um conceito novo para você, por favor, leia 🏢 Organizações (Multi-tenancy) para começar.

Você precisa adicionar o escopo UserScope.Organizations ao configurar o cliente Logto:

LogtoViewModel.kt
val logtoConfig = LogtoConfig(
// ...other configs
scopes = listOf(UserScope.Organizations),
)

Uma vez que o usuário esteja autenticado, você pode buscar o token de organização para o usuário:

LogtoViewModel.kt
// Substitua o parâmetro por um ID de organização válido.
// IDs de organização válidos para o usuário podem ser encontrados na reivindicação do token de ID `organizations`.
logtoClient.getOrganizationToken("organization-id") { logtoException, organizationToken ->
logtoException?.let { println(it) }
organizationToken?.let { println(it) }
}

// ou
logtoClient.getOrganizationTokenClaims("organization-id") { logtoException, claims ->
logtoException?.let { println(it) }
claims?.let { println(it) }
}

Recursos de API da organização (Organization API resources)

Para buscar um token de acesso para um recurso de API em uma organização, você pode usar o método getAccessToken com o recurso de API e o ID da organização como parâmetros:

LogtoViewModel.kt
logtoClient.getAccessToken(
'https://shopping.your-app.com/api',
organizationId
) { logtoException, accessToken ->
println("AccessToken:$accessToken")
}

Leituras adicionais

Fluxos do usuário final: fluxos de autenticação, fluxos de conta e fluxos de organização Configurar conectores Autorização (Authorization)