OAuth 2.0 de xstarmail

xstarmail implementa el protocolo OAuth 2.0 con soporte OpenID Connect, permitiendo que aplicaciones de terceros autentiquen usuarios con su cuenta xstarmail — similar a "Iniciar sesión con Google".

🔐

OAuth 2.0

Authorization Code Flow con PKCE

🆔

OpenID Connect

Descubrimiento automático y UserInfo

Simple

Integración en minutos

1. Inicio rápido

1

Registra tu aplicación en la Consola de Desarrolladores. Recibirás un client_id y un client_secret.

2

Redirige al usuario a la página de autorización con tus parámetros.

3

Intercambia el código por un access token en tu backend.

4

Obtén los datos del usuario con el endpoint /api/oauth/userinfo.

2. Registrar aplicación

Ve a la Consola de Desarrolladores y crea una nueva aplicación. Necesitarás:

CampoDescripciónRequerido
nameNombre de tu aplicación
redirect_urisURLs de callback autorizadas
descriptionDescripción corta
website_urlURL de tu sitio web

3. Flujo de autorización

xstarmail implementa el flujo Authorization Code (el más seguro para apps web):

Tu App
/authorize
Usuario da permiso
↓ code
Tu Backend
↓ code
/api/oauth/token
Recibe tokens
↓ token
/api/oauth/userinfo
Datos del usuario

Paso 1: Redirigir al usuario

Envía al usuario a la página de autorización:

URL
https://xstarmail.es/authorize?
  response_type=code
  &client_id=TU_CLIENT_ID
  &redirect_uri=https://tuapp.com/callback
  &scope=openid profile email
  &state=VALOR_ALEATORIO

Paso 2: Recibir el código

Después de aprobar, el usuario vuelve a tu redirect_uri:

URL
https://tuapp.com/callback?code=AUTHORIZATION_CODE&state=VALOR_ALEATORIO

Paso 3: Intercambiar por tokens

HTTP
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=TU_CLIENT_ID
&client_secret=TU_CLIENT_SECRET
&redirect_uri=https://tuapp.com/callback

Respuesta:

JSON
{
  "access_token": "eyJ...",
  "refresh_token": "dGhp...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email"
}

Paso 4: Obtener datos del usuario

HTTP
GET /api/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
JSON
{
  "sub": "clxyz123abc",
  "name": "María García",
  "email": "maria@xstarmail.es",
  "email_verified": true
}

4. Endpoints

GET
/.well-known/openid-configuration

Descubrimiento OIDC — configuración automática

GET
/authorize

Página de consentimiento del usuario

POST
/api/oauth/authorize

Procesar decisión del usuario (aprobar/denegar)

POST
/api/oauth/token

Intercambiar código por tokens / renovar tokens

GET
/api/oauth/userinfo

Obtener perfil del usuario autenticado

POST
/api/oauth/revoke

Revocar un access o refresh token

GET
/api/oauth/apps

Listar tus aplicaciones (requiere sesión)

POST
/api/oauth/apps

Crear nueva aplicación OAuth

POST /api/oauth/token — Parámetros

ParámetroTipoDescripción
grant_typestringauthorization_code o refresh_token
codestringCódigo de autorización (para grant_type=authorization_code)
client_idstringID de tu aplicación
client_secretstringSecreto de tu aplicación
redirect_uristringDebe coincidir con la URI registrada
refresh_tokenstringToken de refresco (para grant_type=refresh_token)
code_verifierstringVerificador PKCE (para clientes públicos)

GET /api/oauth/userinfo — Respuesta

CampoScopeDescripción
subopenidIdentificador único del usuario
nameprofileNombre del usuario
emailemailDirección de correo @xstarmail.es
email_verifiedemailSiempre true

5. Scopes

openid

Requerido. Devuelve el campo sub (identificador único del usuario).

Campos: sub

profile

Información del perfil: nombre del usuario.

Campos: name, updated_at

email

Dirección de correo electrónico del usuario.

Campos: email, email_verified

6. PKCE (Proof Key for Code Exchange)

Para aplicaciones públicas (SPAs, apps móviles) que no pueden guardar un client_secret de forma segura, usa PKCE:

JavaScript
// 1. Genera code_verifier (random string)
const verifier = crypto.randomUUID() + crypto.randomUUID();

// 2. Genera code_challenge (SHA-256 hash del verifier)
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest('SHA-256', encoder.encode(verifier));
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

// 3. Incluye en la URL de autorización
const authUrl = `https://xstarmail.es/authorize?
  response_type=code
  &client_id=TU_CLIENT_ID
  &redirect_uri=https://tuapp.com/callback
  &scope=openid profile email
  &code_challenge=${challenge}
  &code_challenge_method=S256`;

// 4. Al intercambiar el código, incluye el verifier
fetch('https://xstarmail.es/api/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: CODE,
    client_id: 'TU_CLIENT_ID',
    redirect_uri: 'https://tuapp.com/callback',
    code_verifier: verifier,
  }),
});

7. Botón «Iniciar sesión con xstarmail»

Copia este HTML para añadir el botón de login a tu web:

HTML
<!-- Botón oscuro -->
<a href="https://xstarmail.es/authorize?response_type=code&client_id=TU_CLIENT_ID&redirect_uri=TU_REDIRECT&scope=openid profile email"
   style="display:inline-flex;align-items:center;gap:12px;padding:10px 20px;
          background:#0A0A0A;border:1px solid #262626;border-radius:8px;
          color:#fff;font-size:14px;font-weight:500;text-decoration:none;
          font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;">
  <svg viewBox="0 0 100 100" width="16" height="16" fill="none" stroke="currentColor" stroke-width="5">
    <rect x="5" y="20" width="90" height="60" rx="8"/><polyline points="5,20 50,55 95,20"/>
  </svg>
  Iniciar sesión con xstarmail
</a>

8. Ejemplos de código

Node.js (Express)

JavaScript
const express = require('express');
const app = express();

const CLIENT_ID = 'tu_client_id';
const CLIENT_SECRET = 'tu_client_secret';
const REDIRECT_URI = 'http://localhost:3000/callback';

// Redirigir al usuario para login
app.get('/login', (req, res) => {
  const state = crypto.randomUUID();
  req.session.oauthState = state;
  res.redirect(`https://xstarmail.es/authorize?response_type=code&client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=openid profile email&state=${state}`);
});

// Callback — intercambiar código por token
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  if (state !== req.session.oauthState) {
    return res.status(403).send('State mismatch');
  }

  // Intercambiar código por token
  const tokenRes = await fetch('https://xstarmail.es/api/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      redirect_uri: REDIRECT_URI,
    }),
  });
  const tokens = await tokenRes.json();

  // Obtener perfil del usuario
  const userRes = await fetch('https://xstarmail.es/api/oauth/userinfo', {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  });
  const user = await userRes.json();

  // user = { sub: "...", name: "María", email: "maria@xstarmail.es", email_verified: true }
  req.session.user = user;
  res.redirect('/dashboard');
});

app.listen(3000);

Python (Flask)

Python
from flask import Flask, redirect, request, session
import requests, secrets

app = Flask(__name__)
app.secret_key = 'tu-secret-key'

CLIENT_ID = 'tu_client_id'
CLIENT_SECRET = 'tu_client_secret'
REDIRECT_URI = 'http://localhost:5000/callback'

@app.route('/login')
def login():
    state = secrets.token_urlsafe(32)
    session['oauth_state'] = state
    return redirect(
        f'https://xstarmail.es/authorize?response_type=code'
        f'&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}'
        f'&scope=openid profile email&state={state}'
    )

@app.route('/callback')
def callback():
    if request.args.get('state') != session.get('oauth_state'):
        return 'State mismatch', 403

    # Intercambiar código por token
    token_res = requests.post('https://xstarmail.es/api/oauth/token', data={
        'grant_type': 'authorization_code',
        'code': request.args['code'],
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
        'redirect_uri': REDIRECT_URI,
    })
    tokens = token_res.json()

    # Obtener perfil
    user_res = requests.get('https://xstarmail.es/api/oauth/userinfo',
        headers={'Authorization': f'Bearer {tokens["access_token"]}'})
    user = user_res.json()

    session['user'] = user
    return redirect('/dashboard')

Next.js (App Router)

TypeScript
// app/api/auth/xstarmail/route.ts
import { redirect } from 'next/navigation';

export async function GET() {
  const state = crypto.randomUUID();
  // Guarda state en cookie/session para verificar después
  
  redirect(`https://xstarmail.es/authorize?${new URLSearchParams({
    response_type: 'code',
    client_id: process.env.XSTARMAIL_CLIENT_ID!,
    redirect_uri: process.env.XSTARMAIL_REDIRECT_URI!,
    scope: 'openid profile email',
    state,
  })}`);
}

// app/api/auth/xstarmail/callback/route.ts
import { NextResponse } from 'next/server';

export async function GET(req: Request) {
  const url = new URL(req.url);
  const code = url.searchParams.get('code');

  const tokenRes = await fetch('https://xstarmail.es/api/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code!,
      client_id: process.env.XSTARMAIL_CLIENT_ID!,
      client_secret: process.env.XSTARMAIL_CLIENT_SECRET!,
      redirect_uri: process.env.XSTARMAIL_REDIRECT_URI!,
    }),
  });
  const tokens = await tokenRes.json();

  const userRes = await fetch('https://xstarmail.es/api/oauth/userinfo', {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  });
  const user = await userRes.json();

  // Crear sesión con los datos del usuario
  const response = NextResponse.redirect(new URL('/dashboard', req.url));
  // ... guardar user en cookie/sesión
  return response;
}

9. Códigos de error

ErrorHTTPDescripción
invalid_request400Falta un parámetro obligatorio o formato incorrecto
invalid_client400Client ID no encontrado o client_secret incorrecto
invalid_grant400Código expirado, ya usado, o redirect_uri no coincide
invalid_scope400Scope solicitado no válido
unsupported_response_type400Solo se soporta response_type=code
unsupported_grant_type400Solo authorization_code y refresh_token
access_deniedEl usuario denegó la autorización
invalid_token401Token expirado, revocado o inválido
login_required401El usuario no tiene sesión activa

10. Seguridad

🔒 HTTPS obligatorio

Todas las redirect URIs en producción deben usar HTTPS. Solo se permite HTTP para localhost durante desarrollo.

🎲 State parameter

Siempre usa un valor aleatorio en state y verifica que coincida en el callback para prevenir ataques CSRF.

🔑 Protege tu client_secret

Nunca expongas el client_secret en código frontend. Usa PKCE para apps públicas (SPAs, apps móviles).

⏱️ Tiempos de vida

Los authorization codes expiran en 10 minutos y solo se pueden usar una vez. Los access tokens duran 1 hora. Los refresh tokens duran 30 días.

🔄 Rotación de refresh tokens

Cada vez que se usa un refresh token, se revoca y se emite uno nuevo para mejorar la seguridad.

xstarmail OAuth 2.0 — Consola de Desarrolladores · xstarmail.es