Ressources developpeurs

Une identite standards-first en quelques minutes.

Commencez avec un login OIDC standard et des comptes self-service familiers. Ajoutez des profils geres seulement lorsque votre produit a besoin de personnes a charge, de gestion deleguee ou de continuite de profil.

Commencez par l'OIDC standard. Ajoutez des profils geres seulement lorsque le modele produit l'exige.
manage-tuurio-id

Templates d'identite gratuits et prets pour la production avec Tuurio ID

Lancez un flux CLI pour creer un tenant, provisionner des clients et telecharger des samples prets a l'emploi avec .env pre-rempli.

Commande de demarrage rapide ~5 minutes
npx manage-tuurio-id@1.7.0
Ce que fait la CLI
  1. Choisissez Login, New ou Website.
  2. New cree le tenant + admin et echange le token automatiquement.
  3. Creez des clients SPA ou des clients web server-side.
  4. Activez en option les webhooks pour les templates server-side.
  5. Telechargez le sample GitHub correspondant et ecrivez .env automatiquement.
Templates inclus
React SPA Vue SPA Angular SPA Node.js Python Java / Spring Go PHP

Pour les templates server-side avec webhooks : deployez d'abord, puis mettez a jour l'URL d'endpoint webhook dans la page admin webhook du tenant.

Model Context Protocol

Donnez aux assistants IA un accès contrôlé à Tuurio

MCP Streamable HTTP utilise OAuth dans le navigateur, PKCE, des jetons liés aux ressources et les droits effectifs de l'utilisateur connecté.

  • Utilisez /mcp pour les opérations normales du tenant.
  • Utilisez /mcp/setup uniquement pour l'onboarding guidé.
  • Demandez mcp:write explicitement pour les changements approuvés.
Guide MCP complet
CodexOAuth + PKCE
codex mcp add tuurio-auth --url https://<tenant>.id.tuurio.com/mcp
codex mcp login tuurio-auth --scopes mcp:connect,mcp:write

Pour la lecture seule, omettez mcp:write. Les hôtes avec enregistrement dynamique compatible n'exigent pas de client créé à l'avance.

Integrations d'exemple

Partez d'un sample qui fonctionne, pas d'un extrait abstrait

Le depot public auth_samples fournit des applications de reference pour associations, ecoles, portails membres et outils internes. Le code reste sur GitHub; cette page sert de point d'entree pour les stacks que votre equipe livre reellement.

Ouvrir le depot complet

SPA web

Samples client public pour React, Vue 3, Angular et Next.js avec Authorization Code + PKCE.

Applications web server-side

Samples client confidentiel et session pour Node.js, Python, Java, Go, PHP et Laravel.

Mobile et natif

Samples Android, iOS et Flutter pour les flux de connexion par deep link et app link.

Choisissez votre stack

Tous les exemples sont proches de la production
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}`);
});
                        

Note : le secret doit comporter au moins 32 caracteres, sinon la bibliotheque peut refuser de demarrer.

Conseil : decouvrez automatiquement le end_session_endpoint via /.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) Aucun code manuel requis !

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();
}
                        
Note : Cet exemple montre un client public avec PKCE. Pour une application web en production, nous recommandons un BFF (Backend for Frontend) afin que les jetons restent cote serveur. Details dans le guide de securite.
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();
                        
Note : Cet exemple montre un client public avec PKCE. Pour une application web en production, nous recommandons un BFF (Backend for Frontend) afin que les jetons restent cote serveur. Details dans le guide de securite.
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()
});
                        
Note : Cet exemple montre un client public avec PKCE. Pour une application web en production, nous recommandons un BFF (Backend for Frontend) afin que les jetons restent cote serveur. Details dans le guide de securite.
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)
}
                        

Conseil : AppAuth peut utiliser l'URL de discovery (/.well-known/openid-configuration) afin d'eviter de fixer les endpoints en dur.

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

Conseil : iOS AppAuth prend egalement en charge discovery pour charger automatiquement la configuration.

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

Modeliser de vraies personnes, pas seulement des comptes

Beaucoup de produits peuvent commencer avec un login self-service. Si vous devez plus tard laisser des parents, tuteurs, equipes ou membres responsables agir pour quelqu'un, Tuurio peut le modeliser sans faux comptes.

Compte et profil ne sont pas toujours la meme chose

Gardez les flux OIDC pour les credentials tout en stockant separement le vrai profil la ou les operations en ont besoin.

Profils geres sans identifiants

Creez d'abord les profils de membres, d'eleves, de personnes a charge ou de benevoles qui ne doivent pas encore recevoir leur propre connexion.

Continuite quand une connexion est ajoutee plus tard

Si un profil gere recoit plus tard son propre acces, le meme profil conserve les memes relations et le meme historique.

Controle d'acces integre

Definissez vos propres permissions (par ex. inventory:write ou reports:view) directement dans le dashboard Tuurio.

L'autorisation se fait dans le token. Pas dans votre base de donnees.

Spring Security attend par defaut des authorities avec le prefixe SCOPE_. Comme Tuurio livre les droits dans le claim permissions, utilisez un JwtAuthenticationConverter pour que @PreAuthorize("hasAuthority('inventory:write')") fonctionne sans prefixe.

Les permissions sont livrees dans le claim permissions sous forme de tableau de chaines.
JWT decode (Access Token)

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

Tuurio fournit aussi des claims standard comme email_verified ou preferred_username, afin d'eviter de les dupliquer dans votre base de donnees.

Exemple 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
    }
}
                                    
Reference API

Documentation de reference pour les points d'integration destines aux clients API cote developpeur.

Ouvrir la reference API ->
Guide de securite

Bonnes pratiques pour le stockage des tokens, PKCE et la protection CSRF.

Lire le guide ->
Support

Bloque sur l'integration ? Nous debuggons avec vous.

Contacter le support ->