Preparar Supabase
Al terminar: Tener un proyecto de Supabase con email y contraseña, probado con curl antes de escribir Kotlin.
Hasta aquí la app no ha necesitado ninguna cuenta en ningún sitio. A partir de esta lección sí. Supabase es un servicio en internet que te da, sin montar un servidor tuyo, una base de datos y un sistema de cuentas: guarda los usuarios y sus contraseñas y comprueba quién es quién. Tiene un plan gratuito. A esa comprobación se le llama autenticación: asegurarse de que quien usa la app es quien dice ser, normalmente con un correo y una contraseña.
Vas a montar ese servidor de autenticación y, al terminar, habrás registrado un usuario y entrado con él sin escribir una línea de Kotlin.
El orden es: primero el panel de Supabase (crear el proyecto, copiar dos claves, tocar tres ajustes) y después la Terminal, con curl. Lo que se hace en el panel decide si la app se puede probar o no, y tres ajustes mal puestos dan tres errores distintos que parecen fallos del código. No se instala nada en el proyecto: todo ocurre en el navegador y en la Terminal de Android Studio.
Palabras que van a salir
Cinco palabras aparecen en esta lección y en las siguientes. Léelas ahora y vuelve aquí si alguna se te escapa.
- Token. Un texto largo que el servidor te da al entrar y que la app enseña en cada petición. Es como la pulsera de un festival: no vuelves a dar tu nombre y tu contraseña en cada puerta, enseñas la pulsera.
- Endpoint. Cada dirección del servidor que hace una cosa.
/auth/v1/signupregistra a un usuario y/auth/v1/tokenentrega tokens: cada una es un endpoint («punto de entrada»). - REST. HTTP, con su método, su dirección, su cuerpo y su código de estado, ya lo conoces de Red: HTTP y JSON y Red: HTTP, JSON y URLSession; aquí se usa
POST. Verás cuatro códigos de estado: el400(tu petición está mal), el401(no estás autenticado), el403(el servidor sabe quién eres, o ve que tu token ya no vale, y no te deja pasar) y el422(la petición está bien escrita pero los datos no se aceptan, por ejemplo una contraseña demasiado corta). REST es la costumbre de dar a cada operación su propia dirección, como/auth/v1/signup. - JWT. Es el formato del
access_token: un texto con tres partes separadas por puntos (la cabecera, el contenido con quién eres y hasta cuándo vale, y la firma), y que el servidor firma para que nadie pueda alterarlo. Son dos cosas distintas: la firma impide que lo modifiques a mano, y la caducidad (va dentro del propio texto) hace que deje de valer pasado un rato. - OAuth 2.0. Es el acuerdo estándar para entrar y mantener la sesión con dos tokens: uno de vida corta para cada petición (el
access_token, el de acceso) y otro, el de refresco (refresh_token), para conseguir uno nuevo sin pedirte otra vez la contraseña.
Por qué endpoints y no el SDK
Supabase publica un SDK de Kotlin. Un SDK es una librería que el propio servicio publica para que no tengas que escribir las peticiones a mano. Aquí no se usa, por dos motivos.
El primero es lo que se aprende. Con la API REST lo que queda en la cabeza es el patrón: registrar, entrar, guardar tokens, refrescarlos y cerrar sesión son los mismos cinco pasos contra cualquier servidor con OAuth 2.0. Cambiar Supabase por otro servicio de cuentas (Auth0, Firebase…) o por un servidor propio toca la dirección base del servicio y el nombre de dos campos. Con un SDK, lo que aprendes es el SDK.
El segundo es práctico y ya lo has vivido en la lección La base en cada plataforma: una dependencia que no publica su versión para el simulador de iOS (iosSimulatorArm64) deja el proyecto sin compilar para iOS. Ktor ya está en el proyecto desde HTTP y Ktor: el cliente de PokeAPI y publica para todo.
Paso 1 · Crear el proyecto
Entra en supabase.com y crea una cuenta; el plan gratuito basta. Después pulsa New project y elige organización, nombre y región.
El aprovisionamiento tarda un par de minutos. Mientras tanto, sigue leyendo.
Paso 2 · Las dos claves
Hacen falta dos valores, y el panel los enseña en la pantalla de conexión del proyecto (el botón Connect, arriba) y en Project Settings → API Keys:
| Qué | Para qué |
|---|---|
| Project URL | la base de todas las peticiones: https://abcdefgh.supabase.co |
Clave pública (publishable key, antes llamada anon) | la cabecera apikey de cada petición; empieza por sb_publishable_ |
Cópialos a un bloc de notas: la lección siguiente los lleva a las apps.
Paso 3 · Los tres ajustes que deciden si puedes probar
Estos tres se hacen ahora, no cuando fallen. Todos están en la sección Authentication del panel.
Desactivar «Confirm email»
En la lista de formas de entrar (los «proveedores»: correo y contraseña, Google, etc.; la sección se llama Sign In / Providers o, en algunos paneles, solo Providers), abre Email y desactiva Confirm email mientras desarrollas.
Con la confirmación activada, el registro no devuelve ninguna sesión: la petición sale con 200, el cuerpo llega bien y no trae ningún access_token (el servidor espera a que confirmes el correo). Quien no lo sepa concluye que su código está mal y se pasa una hora mirando cómo se lee la respuesta.
En producción se vuelve a activar, y entonces hace falta un SMTP propio (un servidor de correo): el correo integrado de Supabase tiene un límite bajo por hora y en proyectos nuevos solo entrega a las direcciones de los miembros de tu organización en Supabase. Sin la confirmación, cualquiera puede registrarse con un correo que no es suyo.
Bajar la caducidad del token
En Authentication → Sessions, pon JWT expiry limit en 60 segundos. Si tu panel no deja bajar tanto, pon el mínimo que acepte: la prueba con curl de la lección El cliente que lleva tu sesión (la del 403) solo te hará esperar más.
Con la hora por defecto, comprobar que el refresco funciona exigiría esperar una hora. Con sesenta segundos lo compruebas mientras tomas café.
Es solo para aprender: la documentación de Supabase no recomienda bajar de 5 minutos en una app de verdad. Al terminar el módulo, vuelve a ponerlo en una hora (3600). Y una consecuencia que verás más adelante: la app pedirá un token nuevo cuando le queden menos de 60 segundos de vida, así que con la caducidad en 60 lo pedirá en cada petición. Para aprender es justo lo que se quiere ver.
Añadir la dirección de vuelta
En Authentication → URL Configuration → Redirect URLs, añade:
pokedex://auth/callbackCuando alguien olvida su contraseña, Supabase le manda un correo con un enlace de recuperación; al pulsarlo, el enlace debe volver a tu app. pokedex://auth/callback es la dirección que abre la Pokédex (ya preparaste algo así en Deep links en Android). Supabase solo redirige a las direcciones de esa lista. Las que no estén se ignoran en silencio y el usuario acaba en el «Site URL», la web por defecto del proyecto, que al empezar es localhost:3000 (un servidor de prueba de tu propio ordenador) y en un móvil no abre nada.
Paso 4 · Probar los endpoints con curl
Esto es lo que ahorra la tarde. Depurar una petición con curl cuesta segundos; depurarla dentro de un cliente de Ktor, con serialización, plugins y corrutinas de por medio, cuesta minutos y da mensajes que hablan de otra cosa.
Abre la Terminal de Android Studio (la pestaña de abajo) y pon tus dos valores en dos variables de la Terminal, para no repetirlos (los de aquí son de ejemplo; usa los tuyos). SUPABASE_URL=… guarda un texto con ese nombre (sin espacios alrededor del =), y $SUPABASE_URL lo pone donde lo escribas. Es lo mismo que una variable en Kotlin, pero en la Terminal. Viven solo en esa Terminal: si la cierras o abres otra pestaña, hay que definirlas de nuevo (la lección El cliente que lleva tu sesión te lo recordará):
SUPABASE_URL=https://abcdefgh.supabase.co
SUPABASE_KEY=sb_publishable_xxxxxxxxY registra un usuario. Usa un correo tuyo, con un dominio real (tu.nombre+pokedex@gmail.com sirve: en Gmail, lo que va entre el + y la @ se ignora, así que el correo te llega igual y puedes inventar tantas cuentas de prueba como quieras). Antes de la orden van sus opciones explicadas, una a una:
# curl -s ... : sin barra de progreso (solo la respuesta).
# -X POST : el método de la petición.
# -H "nombre: valor" : añade una cabecera, un dato que viaja aparte del cuerpo.
# -d '...' : el cuerpo de la petición; aquí, un JSON.
# La \ del final de línea dice a la Terminal que la orden sigue en la
# siguiente, para que quepa en pantalla.
curl -s -X POST "$SUPABASE_URL/auth/v1/signup" \
-H "apikey: $SUPABASE_KEY" -H "Content-Type: application/json" \
-d '{"email":"tu.nombre+pokedex@gmail.com","password":"contrasena-larga"}'La respuesta tiene esta forma (los textos largos van recortados):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 60,
"expires_at": 1789000060,
"refresh_token": "v1kqNrTz",
"user": { "id": "7c9e6679-7425-...", "email": "tu.nombre+pokedex@gmail.com", "role": "authenticated" }
}Mira los cinco campos principales, porque son exactamente los que vas a modelar en Kotlin: access_token, refresh_token, expires_in, token_type y user. expires_in son los segundos que vale el token: 60, porque bajaste la caducidad en el paso 3.
Ahora, entrar y refrescar:
# Entrar: el mismo endpoint /token con grant_type=password
curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_KEY" -H "Content-Type: application/json" \
-d '{"email":"tu.nombre+pokedex@gmail.com","password":"contrasena-larga"}'
# Refrescar: el mismo endpoint, con grant_type=refresh_token (pon el tuyo)
curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=refresh_token" \
-H "apikey: $SUPABASE_KEY" -H "Content-Type: application/json" \
-d '{"refresh_token":"v1kqNrTz"}'Fíjate en que entrar y refrescar son el mismo endpoint con distinto grant_type. No es un capricho de Supabase: es OAuth 2.0, y por eso este patrón se repite en todas partes.
Y prueba también a equivocarte, porque los errores son la mitad del trabajo. Con tu contraseña mal:
curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" \
-H "apikey: $SUPABASE_KEY" -H "Content-Type: application/json" \
-d '{"email":"tu.nombre+pokedex@gmail.com","password":"mal"}'{"code":400,"error_code":"invalid_credentials","msg":"Invalid login credentials"}Con una contraseña demasiado corta al registrar otro correo (cambia la contraseña por 123 en la petición de registro):
{"code":422,"error_code":"weak_password","msg":"Password should be at least 6 characters.","weak_password":{"reasons":["length"]}}Y sin la cabecera apikey (quita la línea -H "apikey: …" de cualquiera de ellas):
{"hint":"No `apikey` request header or url param was found.","message":"No API key found in request"}Mira el último con atención. Los dos primeros vienen del servidor de autenticación y comparten forma: code, error_code y msg. El tercero ni siquiera llegó hasta él —lo paró la puerta de entrada del proyecto— y habla otro idioma: message y hint, sin msg y sin error_code.
No es un servicio, son varios detrás de la misma URL. Guárdalo: es el motivo de que el DTO de errores de la lección Autenticación en el núcleo: la API tenga todos los campos opcionales, msg y message incluidos. Un parser que solo conozca msg se queda mudo justo cuando peor viene, que es cuando te has dejado la clave.
Pruébalo: el token que caduca
Con la caducidad en 60 segundos, entra con curl, copia el access_token a una variable, espera un minuto y medio, y úsalo para pedir tu perfil. La cabecera Authorization: Bearer … es la pulsera del festival: la palabra Bearer («portador») y, detrás, el token.
ACCESS_TOKEN=pega-aqui-el-access_token
curl -s "$SUPABASE_URL/auth/v1/user" \
-H "apikey: $SUPABASE_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Devuelve un error 403 (no un 401: el servidor de autenticación de Supabase usa 403 para un token que ya no sirve) con "error_code":"bad_jwt" y un mensaje que dice que el token ha caducado (token is expired). La app no puede limitarse a guardar el token y usarlo: tiene que saber cuándo caduca y renovarlo antes, y tiene que saber qué hacer cuando el servidor lo rechaza igualmente. Es el trabajo de Refrescar el token sin duplicados (renovarlo a tiempo) y de El cliente que lleva tu sesión (qué hacer cuando el servidor lo rechaza).
Prueba ahora a refrescar con el refresh_token de esa misma sesión, con el curl de arriba. Funciona, y devuelve un token de refresco nuevo: cada refresco entrega el siguiente. Gastar el mismo dos veces no siempre sale igual (hay una ventana de gracia y luego un rechazo), y por eso la app pone los refrescos en fila, uno detrás de otro, como verás en la lección del refresco.
Con la clave y la URL en tu bloc de notas, lo de esta lección está hecho. No hay ningún fichero que crear: el dónde guardar esos dos valores en el proyecto es la lección siguiente.
Punto de control
- El proyecto de Supabase existe y está activo.
- «Confirm email» está desactivado y «JWT expiry limit» vale 60 (o lo mínimo que te dejó poner).
pokedex://auth/callbackestá en la lista de Redirect URLs.- El
curlde registro devuelve unaccess_tokenno vacío. - El
curlde entrar con la contraseña mal devuelveInvalid login credentials. - Tienes anotados el Project URL y la clave pública, y no la de servicio.
- Sin cambios en el proyecto: siguen 80 tests en la JVM, 44 en Android host y 47 en iOS. Si quieres comprobarlo, en la Terminal de Android Studio lanza
./gradlew :shared:jvmTest :shared:testAndroidHostTest :shared:iosSimulatorArm64Testy abre los informes, como en las lecciones anteriores.
Comprueba lo que entendiste
6 preguntas · necesitas 5 para completar la lección
1.Con «Confirm email» activado, ¿qué devuelve el registro?
2.¿Por qué la app usa la clave pública y nunca la de servicio?
3.¿Qué diferencia hay entre entrar y refrescar en la API de Supabase?
4.Con
curlviste que un login fallido devuelvemsg, y que sin la cabeceraapikeyla respuesta traemessageyhint. ¿Qué consecuencia tiene para el código?5.¿Por qué se prueban los endpoints con curl antes de escribir Kotlin?
6.Has desactivado «Confirm email» para desarrollar. ¿Por qué hay que volver a activarlo antes de publicar la app?