Entwickler-Ressourcen

Standards-first Identity in Minuten.

Starten Sie mit standardkonformem OIDC-Login und vertrauten Self-Service-Accounts. Ergänzen Sie Managed Profiles nur dann, wenn Ihr Produkt Angehörige, delegierte Bearbeitung oder Profilkontinuitaet braucht.

Nutzen Sie zuerst Standard-OIDC. Managed Profiles kommen nur dazu, wenn das Produktmodell sie braucht.
manage-tuurio-id

Kostenlose produktionsnahe Identity-Templates mit Tuurio ID

Führe einen CLI-Flow aus, um einen Tenant zu erstellen, Clients zu provisionieren und sofort lauffähige Samples mit vorausgefuellter .env zu laden.

Schnellstart-Befehl ~5 Minuten
npx manage-tuurio-id@1.7.0
Was die CLI erledigt
  1. Wähle Login, New oder Website.
  2. New erstellt Tenant + Admin und tauscht den Token automatisch.
  3. Erstelle SPA- oder serverseitige Web-App-Clients.
  4. Aktiviere optional Webhooks für serverseitige Templates.
  5. Lade das passende GitHub-Sample und schreibe .env automatisch.
Enthaltene Templates
React SPA Vue SPA Angular SPA Node.js Python Java / Spring Go PHP

Bei serverseitigen Templates mit Webhooks: Erst deployen, dann die Webhook-Endpoint-URL in der Tenant-Admin-Webhook-Seite aktualisieren.

Model Context Protocol

KI-Assistenten kontrollierten Zugriff auf Tuurio geben

Streamable HTTP MCP nutzt Browser-OAuth, PKCE, ressourcengebundene Tokens und die effektiven Rechte des angemeldeten Nutzers.

  • /mcp für den normalen Mandantenbetrieb verwenden.
  • /mcp/setup ausschließlich für das geführte Onboarding verwenden.
  • mcp:write nur für freigegebene Änderungen ausdrücklich anfordern.
Vollständiger MCP-Guide
CodexOAuth + PKCE
codex mcp add tuurio-auth --url https://<tenant>.id.tuurio.com/mcp
codex mcp login tuurio-auth --scopes mcp:connect,mcp:write

Für reinen Lesezugriff mcp:write weglassen. Hosts mit kompatibler dynamischer Registrierung benötigen keinen vorab angelegten Client.

Beispiel-Integrationen

Starte mit einem funktionierenden Sample statt mit einem abstrakten Snippet

Das öffentliche Repository auth_samples liefert Referenz-Apps für Vereine, Schulen, Mitgliederportale und interne Tools. Der Code bleibt auf GitHub; diese Seite ist der Einstieg zu den Stacks, die euer Team wirklich ausliefert.

Gesamtes Repository öffnen

Web-SPAs

Public-Client-Samples für React, Vue 3, Angular und Next.js mit Authorization Code + PKCE.

Serverseitige Web-Apps

Samples für vertrauliche Clients und Session-basierte Anwendungen mit Node.js, Python, Java, Go, PHP und Laravel.

Mobile und native Apps

Android-, iOS- und Flutter-Samples für Deep-Link- und App-Link-Login-Flows.

Wähle deinen Stack

Alle Beispiele sind produktionsnah
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}`);
});
                        

Hinweis: Das secret sollte mindestens 32 Zeichen lang sein, sonst kann die Bibliothek den Start verweigern.

Tipp: Das end_session_endpoint lässt sich automatisch über /.well-known/openid-configuration finden.
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) Kein manueller Code nötig!

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();
}
                        
Hinweis: Dieses Beispiel zeigt einen Public Client mit PKCE. Für produktive Web-Apps empfehlen wir ein BFF (Backend for Frontend), damit Tokens serverseitig bleiben. Details im Security Guide.
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();
                        
Hinweis: Dieses Beispiel zeigt einen Public Client mit PKCE. Für produktive Web-Apps empfehlen wir ein BFF (Backend for Frontend), damit Tokens serverseitig bleiben. Details im Security Guide.
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()
});
                        
Hinweis: Dieses Beispiel zeigt einen Public Client mit PKCE. Für produktive Web-Apps empfehlen wir ein BFF (Backend for Frontend), damit Tokens serverseitig bleiben. Details im Security Guide.
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)
}
                        

Tipp: AppAuth kann die Discovery-URL (/.well-known/openid-configuration) nutzen, damit Endpoints nicht fest hinterlegt werden muessen.

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

Tipp: Auch iOS AppAuth unterstützt Discovery, um die Konfiguration automatisch zu laden.

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

Reale Menschen modellieren, nicht nur Accounts

Viele Produkte starten mit Self-Service-Login. Wenn später Eltern, Sorgeberechtigte, Mitarbeitende oder verantwortliche Mitglieder für andere handeln muessen, kann Tuurio das ohne Fake-Accounts abbilden.

Account und Profil sind nicht immer dasselbe

Behalten Sie OIDC-Login-Flows für Zugangsdaten bei und speichern Sie das reale Profil separat dort, wo operative Prozesse es brauchen.

Managed Profiles ohne Zugangsdaten

Legen Sie zuerst Profile für Mitglieder, Schüler, Angehörige oder Freiwillige an, die noch keinen eigenen Login erhalten sollen.

Kontinuität, wenn später ein Login kommt

Wenn ein verwaltetes Profil später eigenen Zugang erhält, bleibt dasselbe Profil mit denselben Beziehungen und derselben Historie bestehen.

Eingebaute Zugriffskontrolle

Definieren Sie eigene Berechtigungen (z. B. inventory:write oder reports:view) direkt im Tuurio-Dashboard.

Autorisierung passiert im Token. Nicht in Ihrer Datenbank.

Spring Security erwartet standardmäßig Authorities mit SCOPE_-Praefix. Da Tuurio Rechte im permissions-Claim liefert, sollten Sie einen JwtAuthenticationConverter verwenden, damit @PreAuthorize("hasAuthority('inventory:write')") ohne Praefix funktioniert.

Die Rechte landen im permissions-Claim als Array von Strings.
Dekodiertes JWT (Access Token)

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

Tuurio liefert zusätzlich Standard-Claims wie email_verified oder preferred_username, sodass diese nicht separat in Ihrer Datenbank gepflegt werden muessen.

Backend-Beispiel (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
    }
}
                                    
API-Referenz

Referenzdokumentation für die Integrationsendpunkte, die für externe Entwickler-Clients gedacht sind.

API-Referenz öffnen ->
Security Guide

Best Practices für Token-Storage, PKCE und CSRF-Schutz.

Guide lesen ->
Support

Hängen Sie in der Integration fest? Wir debuggen mit.

Kontakt aufnehmen ->