Recursos para desarrolladores

Identidad standards-first en minutos.

Empieza con login OIDC estandar y cuentas self-service familiares. Anade perfiles gestionados solo cuando tu producto necesite dependientes, gestion delegada o continuidad del perfil.

Usa primero OIDC estandar. Anade perfiles gestionados solo cuando el modelo del producto lo requiera.
manage-tuurio-id

Plantillas de identidad gratuitas y listas para produccion con Tuurio ID

Ejecuta un flujo CLI para crear un tenant, aprovisionar clientes y descargar samples listos para usar con .env prellenado.

Comando de inicio rapido ~5 minutos
npx manage-tuurio-id@1.7.0
Que hace la CLI
  1. Elige Login, New o Website.
  2. New crea tenant + admin e intercambia el token automaticamente.
  3. Crea clientes SPA o clientes web del lado servidor.
  4. Activa opcionalmente webhooks para plantillas server-side.
  5. Descarga el sample de GitHub y escribe .env automaticamente.
Plantillas incluidas
React SPA Vue SPA Angular SPA Node.js Python Java / Spring Go PHP

Para plantillas server-side con webhooks: primero despliega y despues actualiza la URL del endpoint webhook en la pagina admin del tenant.

Model Context Protocol

Da a los asistentes de IA acceso controlado a Tuurio

MCP Streamable HTTP usa OAuth en el navegador, PKCE, tokens vinculados a recursos y los permisos efectivos del usuario conectado.

  • Usa /mcp para las operaciones normales del tenant.
  • Usa /mcp/setup solo para el onboarding guiado.
  • Solicita mcp:write explícitamente para cambios aprobados.
Guía MCP completa
CodexOAuth + PKCE
codex mcp add tuurio-auth --url https://<tenant>.id.tuurio.com/mcp
codex mcp login tuurio-auth --scopes mcp:connect,mcp:write

Para solo lectura, omite mcp:write. Los hosts con registro dinámico compatible no requieren un cliente creado previamente.

Integraciones de ejemplo

Empieza con un sample que funciona, no con un snippet abstracto

El repositorio publico auth_samples ofrece apps de referencia para asociaciones, escuelas, portales de miembros y herramientas internas. El codigo se queda en GitHub; esta pagina es el punto de entrada para los stacks que tu equipo entrega de verdad.

Abrir repositorio completo

SPA web

Samples de cliente publico para React, Vue 3, Angular y Next.js con Authorization Code + PKCE.

Aplicaciones web del lado servidor

Samples de cliente confidencial y aplicaciones con sesion para Node.js, Python, Java, Go, PHP y Laravel.

Aplicaciones moviles y nativas

Samples de Android, iOS y Flutter para flujos de acceso con deep links y app links.

Elige tu stack

Todos los ejemplos son cercanos a produccion
server.js (Express + express-openid-connect) npm install express-openid-connect

const { auth } = require('express-openid-connect');

const config = {
  authRequired: false,
  auth0Logout: true,
  secret: 'YOUR_LONG_RANDOM_STRING',
  baseURL: 'http://localhost:3000',
  clientID: 'CLIENT_ID_FROM_DASHBOARD',
  issuerBaseURL: 'https://{your-tenant}.id.tuurio.com',
  // Logout requires the ID token from the validated login session.
};

// Add the auth middleware and you are done.
app.use(auth(config));

app.get('/', (req, res) => {
  res.send(req.oidc.isAuthenticated() ? 'Signed in as ' + req.oidc.user.name : 'Not signed in');
});

// Logout (OIDC RP-initiated)
app.get('/logout', async (req, res) => {
  const idTokenHint = req.oidc.idToken;
  if (!idTokenHint) return res.redirect('/');
  const issuer = "https://{your-tenant}.id.tuurio.com";
  const discovery = await fetch(`${issuer}/.well-known/openid-configuration`).then(r => r.json());
  const endSession = discovery.end_session_endpoint;
  const params = new URLSearchParams({
    client_id: 'CLIENT_ID_FROM_DASHBOARD',
    id_token_hint: idTokenHint,
    post_logout_redirect_uri: 'https://example.com/logout/success'
  });
  res.redirect(`${endSession}?${params}`);
});
                        

Nota: el secret debe tener al menos 32 caracteres; de lo contrario, la biblioteca puede rechazar el arranque.

Consejo: descubre automaticamente el end_session_endpoint mediante /.well-known/openid-configuration.
app.py (Flask + Authlib) pip install Authlib Flask Flask-Session redis requests

import os
from authlib.integrations.flask_client import OAuth
from flask_session import Session
from redis import Redis
import requests
from urllib.parse import urlencode

# Flask's default session is a signed browser cookie. Use a server-side store
# before retaining the validated ID token for RP-initiated logout.
app.config.update(
    SESSION_TYPE="redis",
    SESSION_REDIS=Redis.from_url(os.environ["REDIS_URL"]),
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="Lax",
)
Session(app)

oauth = OAuth(app)
oauth.register(
    name='tuurio',
    client_id='CLIENT_ID_FROM_DASHBOARD',
    client_secret='CLIENT_SECRET',
    server_metadata_url='https://{tenant}.id.tuurio.com/.well-known/openid-configuration',
    # Keep the validated ID token in the encrypted server session for logout.
    client_kwargs={'scope': 'openid profile email'}
)

@app.route('/login')
def login():
    redirect_uri = url_for('callback', _external=True)
    return oauth.tuurio.authorize_redirect(redirect_uri)

@app.route('/callback')
def callback():
    token = oauth.tuurio.authorize_access_token()
    session["tuurio_id_token"] = token["id_token"]
    user = token['userinfo']
    return f'Hello, {user["name"]}'

@app.route('/logout')
def logout():
    id_token_hint = session.pop("tuurio_id_token", None)
    if not id_token_hint:
        return redirect(url_for("index"))
    discovery = requests.get("https://{tenant}.id.tuurio.com/.well-known/openid-configuration").json()
    end_session = discovery["end_session_endpoint"]
    params = urlencode({
        "client_id": "CLIENT_ID_FROM_DASHBOARD",
        "id_token_hint": id_token_hint,
        "post_logout_redirect_uri": "https://example.com/logout/success",
    })
    return redirect(f"{end_session}?{params}")
                        
application.yml (Spring Boot Starter OAuth2 Client) No hace falta codigo manual.

spring:
  security:
    oauth2:
      client:
        registration:
          tuurio:
            client-id: CLIENT_ID_FROM_DASHBOARD
            client-secret: CLIENT_SECRET
            scope: [openid, profile, email]
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            # Spring's OIDC logout handler supplies the validated ID token hint.
        provider:
          tuurio:
            issuer-uri: https://{your-tenant}.id.tuurio.com
                        

@Bean
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler(
    ClientRegistrationRepository registrations) {
  var handler = new OidcClientInitiatedLogoutSuccessHandler(registrations);
  handler.setPostLogoutRedirectUri("https://example.com/logout/success");
  return handler;
}

@Bean
SecurityFilterChain securityFilterChain(
    HttpSecurity http,
    OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler) throws Exception {
  http
      .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
      .oauth2Login(Customizer.withDefaults())
      .logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler));
  return http.build();
}
                        
Nota: Este ejemplo muestra un cliente publico con PKCE. Para aplicaciones web en produccion recomendamos un BFF (Backend for Frontend) para mantener los tokens en el servidor. Detalles en la guia de seguridad.
auth.ts (React SPA + oidc-client-ts, PKCE) npm install oidc-client-ts

// end_session_endpoint via discovery:
// const issuer = "https://{tenant}.id.tuurio.com";
// const discovery = await fetch(`${issuer}/.well-known/openid-configuration`).then(r => r.json());
// const endSessionEndpoint = discovery.end_session_endpoint;
                        

import { UserManager } from "oidc-client-ts";

const mgr = new UserManager({
  authority: "https://{tenant}.id.tuurio.com",
  client_id: "CLIENT_ID_FROM_DASHBOARD",
  redirect_uri: "http://localhost:5173/auth/callback",
  post_logout_redirect_uri: "http://localhost:5173/",
  response_type: "code",
  scope: "openid profile email",
  automaticSilentRenew: true
});

export const login = () => mgr.signinRedirect();
export const handleCallback = () => mgr.signinRedirectCallback();
// Logout uses end_session_endpoint from discovery
export const logout = () => mgr.signoutRedirect();
                        
Nota: Este ejemplo muestra un cliente publico con PKCE. Para aplicaciones web en produccion recomendamos un BFF (Backend for Frontend) para mantener los tokens en el servidor. Detalles en la guia de seguridad.
useAuth.ts (Vue 3 + oidc-client-ts) npm install oidc-client-ts

import { UserManager } from "oidc-client-ts";

const mgr = new UserManager({
  authority: "https://{tenant}.id.tuurio.com",
  client_id: "CLIENT_ID_FROM_DASHBOARD",
  redirect_uri: "http://localhost:5173/auth/callback",
  post_logout_redirect_uri: "http://localhost:5173/",
  response_type: "code",
  scope: "openid profile email"
});

export const useAuth = () => ({
  login: () => mgr.signinRedirect(),
  handleCallback: () => mgr.signinRedirectCallback(),
  // Logout uses end_session_endpoint from discovery
  logout: () => mgr.signoutRedirect()
});
                        
Nota: Este ejemplo muestra un cliente publico con PKCE. Para aplicaciones web en produccion recomendamos un BFF (Backend for Frontend) para mantener los tokens en el servidor. Detalles en la guia de seguridad.
auth.config.ts (Angular + angular-oauth2-oidc) npm install angular-oauth2-oidc

import { AuthConfig, OAuthService } from "angular-oauth2-oidc";

export const authConfig: AuthConfig = {
  issuer: "https://{tenant}.id.tuurio.com",
  clientId: "CLIENT_ID_FROM_DASHBOARD",
  redirectUri: window.location.origin + "/auth/callback",
  postLogoutRedirectUri: "http://localhost:5173/",
  responseType: "code",
  scope: "openid profile email"
};

export const initLogout = async (oauthService: OAuthService) => {
  const discovery = await fetch(`${authConfig.issuer}/.well-known/openid-configuration`).then(r => r.json());
  oauthService.logoutUrl = discovery.end_session_endpoint;
};

export const logout = (oauthService: OAuthService) => oauthService.logOut();
                        
Android (Kotlin) + AppAuth OAuth 2.0 + PKCE (Public Client)

val serviceConfig = AuthorizationServiceConfiguration(
    Uri.parse("https://{tenant}.id.tuurio.com/oauth2/authorize"),
    Uri.parse("https://{tenant}.id.tuurio.com/oauth2/token")
)

val request = AuthorizationRequest.Builder(
    serviceConfig,
    "CLIENT_ID_FROM_DASHBOARD",
    ResponseTypeValues.CODE,
    Uri.parse("com.example.app:/oauth2redirect")
)
    .setScope("openid profile email")
    .build()

// val postLogoutRedirectUri = Uri.parse("com.example.app:/logout")

val authService = AuthorizationService(context)
val intent = authService.getAuthorizationRequestIntent(request)
startActivityForResult(intent, RC_AUTH)

// Logout uses the validated ID token retained in AuthState.
AuthorizationServiceConfiguration.fetchFromUrl(
    Uri.parse("https://{tenant}.id.tuurio.com/.well-known/openid-configuration")
) { config, _ ->
    val idTokenHint = authState.lastTokenResponse?.idToken ?: return@fetchFromUrl
    val endSession = EndSessionRequest.Builder(config!!)
        .setIdTokenHint(idTokenHint)
        .setPostLogoutRedirectUri(Uri.parse("com.example.app:/logout"))
        .build()
    val endSessionIntent = authService.getEndSessionRequestIntent(endSession)
    startActivityForResult(endSessionIntent, RC_LOGOUT)
}
                        

Consejo: AppAuth puede usar la URL de discovery (/.well-known/openid-configuration) para no fijar endpoints manualmente.

iOS (Swift) + AppAuth OAuth 2.0 + PKCE (Public Client)

let config = OIDServiceConfiguration(
  authorizationEndpoint: URL(string: "https://{tenant}.id.tuurio.com/oauth2/authorize")!,
  tokenEndpoint: URL(string: "https://{tenant}.id.tuurio.com/oauth2/token")!
)

let request = OIDAuthorizationRequest(
  configuration: config,
  clientId: "CLIENT_ID_FROM_DASHBOARD",
  scopes: [OIDScopeOpenID, OIDScopeProfile, OIDScopeEmail],
  redirectURL: URL(string: "com.example.app:/oauth2redirect")!,
  responseType: OIDResponseTypeCode,
  additionalParameters: nil
)

// let postLogoutRedirectURL = URL(string: "com.example.app:/logout")!

OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
  // Store authState?.lastTokenResponse?.accessToken
}

// Logout uses the validated ID token retained in AuthState.
OIDAuthorizationService.discoverConfiguration(
  forIssuer: URL(string: "https://{tenant}.id.tuurio.com")!
) { config, _ in
  guard let config = config else { return }
  let endSession = OIDEndSessionRequest(
    configuration: config,
    idTokenHint: authState?.lastTokenResponse?.idToken,
    postLogoutRedirectURL: URL(string: "com.example.app:/logout")!,
    additionalParameters: nil
  )
  self.present(OIDAuthorizationService.present(endSession, presenting: self) { _, _ in }, animated: true)
}
                        

Consejo: iOS AppAuth tambien admite discovery para cargar la configuracion automaticamente.

Flutter (Dart) + flutter_appauth flutter pub add flutter_appauth

import 'package:flutter_appauth/flutter_appauth.dart';

final appAuth = FlutterAppAuth();

final result = await appAuth.authorizeAndExchangeCode(
  AuthorizationTokenRequest(
    'CLIENT_ID_FROM_DASHBOARD',
    'com.example.app:/oauth2redirect',
    issuer: 'https://{tenant}.id.tuurio.com',
    scopes: ['openid', 'profile', 'email'],
  ),
);

// Logout (OIDC RP-initiated)
await appAuth.endSession(EndSessionRequest(
  idTokenHint: result?.idToken,
  postLogoutRedirectUrl: 'com.example.app:/logout',
  issuer: 'https://{tenant}.id.tuurio.com',
));
                        
main.go (golang.org/x/oauth2) go get golang.org/x/oauth2

var config = &oauth2.Config{
    ClientID:     "CLIENT_ID",
    ClientSecret: "CLIENT_SECRET",
    RedirectURL:  "http://localhost:3000/callback",
    Scopes:       []string{"openid", "profile", "email"},
    // Retain the validated ID token in the server-side session for logout.
    Endpoint: oauth2.Endpoint{
        AuthURL:  "https://{tenant}.id.tuurio.com/oauth2/authorize",
        TokenURL: "https://{tenant}.id.tuurio.com/oauth2/token",
    },
}
// Nutze config.AuthCodeURL(...) und config.Exchange(...)

// Logout (OIDC RP-initiated). idTokenHint comes from validated server session state.
func logoutURL(idTokenHint string) string {
    if idTokenHint == "" { return "/" }
    resp, _ := http.Get("https://{tenant}.id.tuurio.com/.well-known/openid-configuration")
    defer resp.Body.Close()
    var discovery struct{ EndSessionEndpoint string `json:"end_session_endpoint"` }
    json.NewDecoder(resp.Body).Decode(&discovery)
    values := url.Values{
        "client_id": {"CLIENT_ID_FROM_DASHBOARD"},
        "id_token_hint": {idTokenHint},
        "post_logout_redirect_uri": {"https://example.com/logout/success"},
    }
    return discovery.EndSessionEndpoint + "?" + values.Encode()
}
                        
index.php (league/oauth2-client) composer require league/oauth2-client

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'CLIENT_ID',
    'clientSecret'            => 'CLIENT_SECRET',
    'redirectUri'             => 'https://example.com/callback',
    'urlAuthorize'            => 'https://{tenant}.id.tuurio.com/oauth2/authorize',
    'urlAccessToken'          => 'https://{tenant}.id.tuurio.com/oauth2/token',
    'urlResourceOwnerDetails' => 'https://{tenant}.id.tuurio.com/userinfo',
    // Retain the validated ID token in the server-side PHP session for logout.
]);

// Logout (OIDC RP-initiated)
$discovery = json_decode(file_get_contents("https://{tenant}.id.tuurio.com/.well-known/openid-configuration"), true);
$endSession = $discovery['end_session_endpoint'];
$idTokenHint = $_SESSION['tuurio_id_token'] ?? '';
unset($_SESSION['tuurio_id_token']);
if ($idTokenHint === '') {
    header('Location: /');
    exit;
}
$params = http_build_query([
    'client_id' => 'CLIENT_ID_FROM_DASHBOARD',
    'id_token_hint' => $idTokenHint,
    'post_logout_redirect_uri' => 'https://example.com/logout/success',
]);
header('Location: ' . $endSession . '?' . $params);
                        

Modela personas reales, no solo cuentas

Muchos productos pueden empezar con login self-service. Si mas adelante padres, tutores, personal o miembros responsables deben actuar por otra persona, Tuurio puede modelarlo sin cuentas falsas.

Cuenta y perfil no siempre son lo mismo

Mantiene los flujos OIDC para credenciales mientras almacenas el perfil real por separado alli donde las operaciones lo necesitan.

Perfiles gestionados sin credenciales

Crea primero perfiles de miembros, estudiantes, dependientes o voluntarios que todavia no deben recibir su propio login.

Continuidad cuando el login se anade despues

Si un perfil gestionado recibe mas adelante su propio acceso, el mismo perfil mantiene las mismas relaciones e historial.

Control de acceso integrado

Define tus propios permisos (por ejemplo inventory:write o reports:view) directamente en el panel de Tuurio.

La autorizacion ocurre en el token. No en tu base de datos.

Spring Security espera authorities con el prefijo SCOPE_ por defecto. Como Tuurio entrega los derechos en el claim permissions, usa un JwtAuthenticationConverter para que @PreAuthorize("hasAuthority('inventory:write')") funcione sin prefijo.

Los permisos se entregan en el claim permissions como un array de cadenas.
JWT decodificado (Access Token)

{
  "sub": "user_12345",
  "iss": "https://dein-tenant.id.tuurio.com",
  "permissions": [
    "inventory:write",
    "reports:view"
  ],
  "roles": ["ADMIN"]
}
                                

Tuurio tambien entrega claims estandar como email_verified o preferred_username, para que no tengas que duplicarlos en tu base de datos.

Ejemplo backend (Spring Security)

@PreAuthorize("hasAuthority('inventory:write')")
@PostMapping("/inventory")
public void updateStock() {
    // Tuurio hat's erlaubt!
}
                                    
SecurityConfig (Kotlin)

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .oauth2ResourceServer { oauth2 ->
                oauth2.jwt { jwt ->
                    jwt.jwtAuthenticationConverter(tuurioAuthenticationConverter())
                }
            }
        return http.build()
    }

    private fun tuurioAuthenticationConverter(): Converter {
        val converter = JwtAuthenticationConverter()
        converter.setJwtGrantedAuthoritiesConverter { jwt ->
            // Extrahiert das "permissions" Array aus dem Token
            val permissions = jwt.getClaim>("permissions") ?: emptyList()

            // Mapping zu SimpleGrantedAuthority
            permissions.map { SimpleGrantedAuthority(it) }
        }
        return converter
    }
}
                                    
Referencia API

Documentacion de referencia para los endpoints de integracion destinados a clientes API orientados a desarrolladores.

Abrir referencia API ->
Guia de seguridad

Buenas practicas para almacenamiento de tokens, PKCE y proteccion CSRF.

Leer guia ->
Soporte

Bloqueado en la integracion? Depuramos contigo.

Contactar soporte ->