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.
Lancez un flux CLI pour creer un tenant, provisionner des clients et telecharger des samples prets a l'emploi avec .env pre-rempli.
npx manage-tuurio-id@latest
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.
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.
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}`);
});
Note : le secret doit comporter au moins 32 caracteres, sinon la bibliotheque peut refuser de demarrer.
end_session_endpoint via /.well-known/openid-configuration.
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)
}
Conseil : AppAuth peut utiliser l'URL de discovery (/.well-known/openid-configuration) afin d'eviter de fixer les endpoints en dur.
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)
}
Conseil : iOS AppAuth prend egalement en charge discovery pour charger automatiquement la configuration.
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'));
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.
Gardez les flux OIDC pour les credentials tout en stockant separement le vrai profil la ou les operations en ont besoin.
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.
Si un profil gere recoit plus tard son propre acces, le meme profil conserve les memes relations et le meme historique.
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.
permissions sous forme de tableau de chaines.
{
"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.
@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
}
}
Documentation de reference pour les points d'integration destines aux clients API cote developpeur.
Ouvrir la reference API ->Bonnes pratiques pour le stockage des tokens, PKCE et la protection CSRF.
Lire le guide ->