Wenn du OIDC schon kennst, brauchst du nichts Neues lernen.
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
};
// Auth Middleware einbinden - fertig!
app.use(auth(config));
app.get('/', (req, res) => {
res.send(req.oidc.isAuthenticated() ? 'Eingeloggt als ' + req.oidc.user.name : 'Nicht eingeloggt');
});
// 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 meckert die Library beim Start.
end_session_endpoint findest du automatisch in
/.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)
}
Tipp: AppAuth kann die Discovery-URL nutzen (/.well-known/openid-configuration),
damit du die Endpoints nicht manuell angeben musst.
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'));
Definiere deine eigenen Berechtigungen (z.B. inventory:write oder reports:view) direkt im Tuurio Dashboard.
Autorisierung passiert im Token. Nicht in deiner Datenbank.
Spring Security erwartet standardmaessig Authorities mit SCOPE_-Praefix.
Da Tuurio die Rechte als permissions-Claim liefert, solltest du einen
JwtAuthenticationConverter verwenden, um @PreAuthorize("hasAuthority('inventory:write')")
ohne Praefix zu nutzen.
permissions als Array von Strings.
{
"sub": "user_12345",
"iss": "https://dein-tenant.id.tuurio.com",
"permissions": [
"inventory:write",
"reports:view"
],
"roles": ["MANAGER"]
}
Zusaetzlich liefert Tuurio Standard-Claims wie email_verified oder preferred_username,
damit du diese nicht selbst in der DB pflegen musst.
@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
}
}