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@latest
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.

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 (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
};

// 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 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 returnTo = encodeURIComponent('https://example.com/logout/success');
  res.redirect(`${endSession}?post_logout_redirect_uri=${returnTo}`);
});
                        

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

from authlib.integrations.flask_client import OAuth
import requests

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',
    # Logout (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
    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()
    user = token['userinfo']
    return f'Hello, {user["name"]}'

@app.route('/logout')
def logout():
    discovery = requests.get("https://{tenant}.id.tuurio.com/.well-known/openid-configuration").json()
    end_session = discovery["end_session_endpoint"]
    return redirect(f"{end_session}?post_logout_redirect_uri=https://example.com/logout/success")
                        
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}"
            # Logout (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
        provider:
          tuurio:
            issuer-uri: https://{your-tenant}.id.tuurio.com
                        

@RestController
class LogoutController {
  @GetMapping("/logout")
  void logout(HttpServletResponse response) throws IOException {
    String issuer = "https://{your-tenant}.id.tuurio.com";
    Map discovery = RestClient.create().get()
        .uri(issuer + "/.well-known/openid-configuration")
        .retrieve().body(Map.class);
    String endSession = (String) discovery.get("end_session_endpoint");
    String returnTo = URLEncoder.encode("https://example.com/logout/success", StandardCharsets.UTF_8);
    response.sendRedirect(endSession + "?post_logout_redirect_uri=" + returnTo);
  }
}
                        
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 (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=com.example.app:/logout
AuthorizationServiceConfiguration.fetchFromUrl(
    Uri.parse("https://{tenant}.id.tuurio.com/.well-known/openid-configuration")
) { config, _ ->
    val endSession = EndSessionRequest.Builder(
        config!!,
        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 (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=com.example.app:/logout
OIDAuthorizationService.discoverConfiguration(
  forIssuer: URL(string: "https://{tenant}.id.tuurio.com")!
) { config, _ in
  guard let config = config else { return }
  let endSession = OIDEndSessionRequest(
    configuration: config,
    idTokenHint: nil,
    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"},
    // Logout (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
    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)
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)
logoutUrl := discovery.EndSessionEndpoint + "?post_logout_redirect_uri=" + url.QueryEscape("https://example.com/logout/success")
                        
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',
    // Logout (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
]);

// 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'];
header('Location: ' . $endSession . '?post_logout_redirect_uri=' . urlencode('https://example.com/logout/success'));
                        

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 ->