Starten Sie mit standardkonformem OIDC-Login und vertrauten Self-Service-Accounts. Ergaenzen Sie Managed Profiles nur dann, wenn Ihr Produkt Angehoerige, delegierte Bearbeitung oder Profilkontinuitaet braucht.
Fuehre einen CLI-Flow aus, um einen Tenant zu erstellen, Clients zu provisionieren und sofort lauffaehige Samples mit vorausgefuellter .env zu laden.
npx manage-tuurio-id@latest
Bei serverseitigen Templates mit Webhooks: Erst deployen, dann die Webhook-Endpoint-URL in der Tenant-Admin-Webhook-Seite aktualisieren.
Das oeffentliche Repository auth_samples liefert Referenz-Apps fuer Vereine, Schulen, Mitgliederportale und interne Tools. Der Code bleibt auf GitHub; diese Seite ist der Einstieg zu den Stacks, die euer Team wirklich ausliefert.
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}`);
});
Hinweis: Das secret sollte mindestens 32 Zeichen lang sein, sonst kann die Bibliothek den Start verweigern.
end_session_endpoint laesst sich automatisch ueber /.well-known/openid-configuration finden.
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")
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);
}
}
// 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();
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()
});
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();
import NextAuth from "next-auth";
const handler = NextAuth({
providers: [
{
id: "tuurio",
name: "Tuurio",
type: "oidc",
issuer: "https://{tenant}.id.tuurio.com",
clientId: "CLIENT_ID_FROM_DASHBOARD",
clientSecret: "CLIENT_SECRET"
}
]
// Logout (OIDC RP-initiated): end_session_endpoint + post_logout_redirect_uri=https://example.com/logout/success
});
export { handler as GET, handler as POST };
// app/api/logout/route.ts
export async function GET() {
const issuer = "https://{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");
return Response.redirect(`${endSession}?post_logout_redirect_uri=${returnTo}`);
}
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)
}
Tipp: AppAuth kann die Discovery-URL (/.well-known/openid-configuration) nutzen, damit Endpoints nicht fest hinterlegt werden muessen.
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)
}
Tipp: Auch iOS AppAuth unterstuetzt Discovery, um die Konfiguration automatisch zu laden.
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',
));
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")
$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'));
Viele Produkte starten mit Self-Service-Login. Wenn spaeter Eltern, Sorgeberechtigte, Mitarbeitende oder verantwortliche Mitglieder fuer andere handeln muessen, kann Tuurio das ohne Fake-Accounts abbilden.
Behalten Sie OIDC-Login-Flows für Zugangsdaten bei und speichern Sie das reale Profil separat dort, wo operative Prozesse es brauchen.
Legen Sie zuerst Profile für Mitglieder, Schüler, Angehörige oder Freiwillige an, die noch keinen eigenen Login erhalten sollen.
Wenn ein verwaltetes Profil später eigenen Zugang erhält, bleibt dasselbe Profil mit denselben Beziehungen und derselben Historie bestehen.
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 standardmaessig Authorities mit SCOPE_-Praefix. Da Tuurio Rechte im permissions-Claim liefert, sollten Sie einen JwtAuthenticationConverter verwenden, damit @PreAuthorize("hasAuthority('inventory:write')") ohne Praefix funktioniert.
permissions-Claim als Array von Strings.
{
"sub": "user_12345",
"iss": "https://dein-tenant.id.tuurio.com",
"permissions": [
"inventory:write",
"reports:view"
],
"roles": ["ADMIN"]
}
Tuurio liefert zusaetzlich Standard-Claims wie email_verified oder preferred_username, sodass diese nicht separat in Ihrer Datenbank gepflegt werden muessen.
@PreAuthorize("hasAuthority('inventory:write')")
@PostMapping("/inventory")
public void updateStock() {
// Tuurio hat's erlaubt!
}
@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
}
}
Referenzdokumentation fuer die Integrationsendpunkte, die fuer externe Entwickler-Clients gedacht sind.
API-Referenz oeffnen ->