diff --git a/fachstelle-server/pom.xml b/fachstelle-server/pom.xml
index e780a1f751b3ab224868fcc2fcb47e0d6913f5ed..17859ec73f2cdbda1c2515a4b273c2d938345b27 100644
--- a/fachstelle-server/pom.xml
+++ b/fachstelle-server/pom.xml
@@ -25,8 +25,8 @@
 
 -->
 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-		 xmlns="http://maven.apache.org/POM/4.0.0"
-		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	xmlns="http://maven.apache.org/POM/4.0.0"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 
 	<parent>
@@ -62,6 +62,43 @@
 		<openapi.version>2.3.0</openapi.version>
 	</properties>
 
+	<repositories>
+		<repository>
+			<id>central</id>
+			<name>Maven Central</name>
+			<url>https://repo1.maven.org/maven2/</url>
+		</repository>
+		<repository>
+			<id>nexus</id>
+			<name>Ozg nexus</name>
+			<url>https://nexus.ozg-sh.de/repository/ozg-releases</url>
+		</repository>
+		<repository>
+			<id>shibboleth-releases</id>
+			<name>Shibboleth Releases Repository</name>
+			<url>https://build.shibboleth.net/maven/releases/</url>
+			<releases>
+				<enabled>true</enabled>
+				<checksumPolicy>warn</checksumPolicy>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+		<repository>
+			<id>shibboleth-thirdparty</id>
+			<name>Shibboleth Thirdparty Repository</name>
+			<url>https://build.shibboleth.net/maven/thirdparty/</url>
+			<releases>
+				<enabled>true</enabled>
+				<checksumPolicy>fail</checksumPolicy>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+	</repositories>
+
 	<dependencies>
 		<!-- Spring -->
 		<dependency>
@@ -113,6 +150,14 @@
 			<groupId>org.springframework.security</groupId>
 			<artifactId>spring-security-web</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-saml2-service-provider</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.hateoas</groupId>
 			<artifactId>spring-hateoas</artifactId>
diff --git a/fachstelle-server/src/main/helm/templates/ingress.yaml b/fachstelle-server/src/main/helm/templates/ingress.yaml
index 2393a91265ca5c749cb24ee629e38674764d45aa..4c3a6ebabaf68b8f2d82c7a044c53f241172786b 100644
--- a/fachstelle-server/src/main/helm/templates/ingress.yaml
+++ b/fachstelle-server/src/main/helm/templates/ingress.yaml
@@ -51,6 +51,62 @@ spec:
                 name: fachstelle-server
                 port: 
                   number: 8080
+          - path: /registrierung
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /saml2
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /login
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /preregister
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /register
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /success
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /static
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
+          - path: /webjars
+            pathType: Prefix
+            backend:
+              service:
+                name: fachstelle-server
+                port:
+                  number: 8080
       host: {{ include "app.baseDomain" . }}
   tls:
     - hosts:
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SecurityConfiguration.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SecurityConfiguration.java
index 800ed89cbf81a82c6cf49d5b0d9168cb5bcc5af9..d036ab0fbf6a1e1b4b429c40397edccc7e22051f 100644
--- a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SecurityConfiguration.java
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SecurityConfiguration.java
@@ -30,13 +30,23 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
+import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
+import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
 
+import de.ozgcloud.fachstelle.security.FachstelleLogoutSuccessHandler;
+import de.ozgcloud.fachstelle.security.InMemoryUserDetailService;
+import de.ozgcloud.fachstelle.security.SecurityProvider;
+import de.ozgcloud.fachstelle.security.UrlAuthenticationSuccessHandler;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
@@ -45,26 +55,60 @@ import lombok.extern.log4j.Log4j2;
 @EnableWebSecurity
 @RequiredArgsConstructor
 public class SecurityConfiguration {
+
+	private final InMemoryUserDetailService userDetailsService;
+	private final UrlAuthenticationSuccessHandler urlAuthenticationSuccessHandler;
 	private final FachstellenProperties properties;
+	private final SpringJwtProperties springJwtProperties;
+
+	@Bean
+	public SecurityProvider securityProvider() {
+		return new SecurityProvider();
+	}
 
 	@Bean
 	public AuthenticationTrustResolver authenticationTrustResolver() {
 		return new AuthenticationTrustResolverImpl();
 	}
 
+	@Bean
+	public Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationRepository registrations) {
+		var registrationResolver = new DefaultRelyingPartyRegistrationResolver(registrations);
+		var authenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(registrationResolver);
+		authenticationRequestResolver.setAuthnRequestCustomizer(context -> context.getAuthnRequest().setForceAuthn(true));
+
+		return authenticationRequestResolver;
+	}
+
 	@Bean
 	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
 		http
-		  .authorizeHttpRequests(authorize -> authorize
-			.requestMatchers("/*", "/index**", "/success", "/actuator/**", "/error", "/favicon.ico", "/fonts/**", "/webjars/**", "/api/**", "/api",
-			  "/api/environment")
-			.permitAll()
-			.requestMatchers("/preregister", "/register").authenticated()
-			.anyRequest().denyAll());
+				.authorizeHttpRequests(authorize -> authorize
+						.requestMatchers("/registrierung", "/static/**", "/webjars/**", "/actuator/**", "/error", "/fonts/**", "/webjars/**",
+								"/api/environment")
+						.permitAll()
+						.requestMatchers("/api/**", "/preregister", "/register", "/success").authenticated()
+						.anyRequest().denyAll());
+
+		http.oauth2ResourceServer(this::setOAuth2ResourceServer);
+		http.saml2Login(samlLogin -> samlLogin.successHandler(urlAuthenticationSuccessHandler))
+				.saml2Logout(Customizer.withDefaults())
+				.logout(logoutConfigurer -> logoutConfigurer.logoutSuccessHandler(getLogoutSuccessHandler()));
 
 		return http.build();
 	}
 
+	private void setOAuth2ResourceServer(OAuth2ResourceServerConfigurer<HttpSecurity> configurer) {
+		configurer.jwt().jwkSetUri(springJwtProperties.getJwkSetUri());
+	}
+
+	FachstelleLogoutSuccessHandler getLogoutSuccessHandler() {
+		var handler = new FachstelleLogoutSuccessHandler(userDetailsService);
+		handler.setDefaultTargetUrl(properties.getLogoutSuccessUrl());
+
+		return handler;
+	}
+
 	@Bean
 	public CorsFilter corsFilter() {
 		var source = new UrlBasedCorsConfigurationSource();
@@ -73,11 +117,10 @@ public class SecurityConfiguration {
 		config.setAllowedOrigins(List.of(properties.getCors()));
 		config.setAllowedMethods(List.of("POST", "OPTIONS", "GET", "HEAD"));
 		config.setAllowedHeaders(
-		  List.of(HttpHeaders.ORIGIN, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, HttpHeaders.AUTHORIZATION, "X-Requested-With", "X-Client"));
+				List.of(HttpHeaders.ORIGIN, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, HttpHeaders.AUTHORIZATION, "X-Requested-With", "X-Client"));
 		config.setExposedHeaders(List.of(HttpHeaders.LOCATION, HttpHeaders.CONTENT_DISPOSITION));
 		source.registerCorsConfiguration("/**", config);
 
 		return new CorsFilter(source);
 	}
-
 }
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SpringJwtProperties.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SpringJwtProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..3740ffaa0362b23155c69f17d22bb25c9535dc19
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/SpringJwtProperties.java
@@ -0,0 +1,21 @@
+package de.ozgcloud.fachstelle;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+@Configuration
+@ConfigurationProperties(prefix = SpringJwtProperties.PREFIX)
+public class SpringJwtProperties {
+
+	static final String PREFIX = "spring.security.oauth2.resourceserver.jwt";
+
+	/**
+	 * Jwt jwk set uri
+	 */
+	private String jwkSetUri = null;
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationController.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationController.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c70ac93dce4f527797e575923616517fe8da76b
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationController.java
@@ -0,0 +1,110 @@
+package de.ozgcloud.fachstelle.registration;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.NoSuchElementException;
+import java.util.UUID;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import de.ozgcloud.fachstelle.FachstellenProperties;
+import de.ozgcloud.fachstelle.security.InMemoryUserDetailService;
+import de.ozgcloud.fachstelle.security.User;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Controller
+@RequiredArgsConstructor
+public class FachstelleRegistrationController {
+
+	public static final String OK = "ok";
+	private final InMemoryUserDetailService userDetailsService;
+	private final FachstellenProperties properties;
+	private final FachstelleRegistrationService registrationService;
+
+	@GetMapping(value = "/registrierung", produces = "text/html; charset=utf-8")
+	public String startRegistration(final Model model) {
+		model.addAttribute("loginUrl", properties.getLoginRedirectUrl());
+		return "index";
+	}
+
+	@GetMapping(value = "/preregister", produces = "text/html; charset=utf-8")
+	public String preregister(final Model model, Authentication auth) {
+		var user = getUser(auth);
+		addRegistrationKey(user);
+
+		model.addAttribute("organizationName", user.getCompanyName());
+		model.addAttribute("organizationLegalForm", user.getLegalFormText());
+		model.addAttribute("organizationRegisterType", user.getRegisterType());
+		model.addAttribute("organizationRegisterNumber", user.getRegisterNumber());
+		model.addAttribute("organizationEmail", user.getEmailAddress());
+		model.addAttribute("organizationAddress", user.getAddress());
+
+		var registration = new Registration(user.getRegistrationKey());
+		model.addAttribute("registration", registration);
+
+		return "preregister";
+	}
+
+	void addRegistrationKey(final User user) {
+		user.setRegistrationKey(UUID.randomUUID().toString());
+		user.setRegistrationKeyExpiresAt(Instant.now().plus(10, ChronoUnit.MINUTES));
+	}
+
+	@GetMapping(value = "/success", produces = "text/html; charset=utf-8")
+	public String success(final Model model, Authentication auth, HttpServletRequest request) {
+		var user = getUser(auth);
+		model.addAttribute("name", user);
+		userDetailsService.logout(user);
+		request.getSession().invalidate();
+
+		return "success";
+	}
+
+	@PostMapping("/register")
+	public String register(@ModelAttribute Registration registration, Model model, final Authentication auth) {
+		var user = getUser(auth);
+
+		var res = canRegister(user, registration.key());
+		if (OK.equals(res) && registrationService.register(user)) {
+			return "success";
+		} else {
+			model.addAttribute("errorMessageKey", res);
+			return "error";
+		}
+	}
+
+	private User getUser(Authentication auth) {
+		try {
+			return userDetailsService.loadUserByUsername(auth.getName());
+		} catch (NoSuchElementException | NullPointerException e) {
+			LOG.error("Error loading user.", e);
+			throw new SecurityException(e);
+		}
+	}
+
+	String canRegister(User user, String registrationKey) {
+		boolean regKeyMatch = StringUtils.isNotEmpty(user.getRegistrationKey()) && user.getRegistrationKey().equals(registrationKey);
+		if (!regKeyMatch) {
+			LOG.error("Registration key invalid");
+			return "error_registration_key_does_not_exist";
+		}
+		boolean regKeyNotExpired = Instant.now().isBefore(user.getRegistrationKeyExpiresAt());
+		if (!regKeyNotExpired) {
+			LOG.error("Registration key expired");
+			return "error_registration_key_expired";
+		}
+
+		return OK;
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteService.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1442335e3f16b49bee17f304bf1d9d5b354f786d
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteService.java
@@ -0,0 +1,47 @@
+package de.ozgcloud.fachstelle.registration;
+
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.RestClientException;
+
+import de.ozgcloud.fachstelle.security.User;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+@RequiredArgsConstructor
+public class FachstelleRegistrationRemoteService {
+
+	static final String REGISTER_FACHSTELLE_URI = "api/fachstellen";
+
+	private final RestClient restClient;
+	private final FachstelleRegistrationRequestMapper requestMapper;
+
+	boolean register(User user) {
+		var request = requestMapper.toFachstelleRegistrationRequest(user);
+
+		try {
+			var response = restClient.post()
+			  .uri(REGISTER_FACHSTELLE_URI)
+			  .contentType(MediaType.APPLICATION_JSON)
+			  .body(request)
+			  .retrieve()
+			  .toBodilessEntity();
+
+			if (response.getStatusCode().is2xxSuccessful()) {
+				LOG.info("Successfully registered Fachstelle {} with id {}", request.getFirmenName(), request.getMukId());
+				return true;
+			}
+
+			LOG.error("Failed to register Fachstelle {} with id {}. Status code: {}", request.getFirmenName(), request.getMukId(),
+			  response.getStatusCode());
+		} catch (RestClientException e) {
+			LOG.error("Error sending registration. {}", e.getMessage(), e);
+		}
+
+		return false;
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapper.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b62bd52a279ac869ee92b635c05db1bfc2ea9b1a
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapper.java
@@ -0,0 +1,20 @@
+package de.ozgcloud.fachstelle.registration;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import de.ozgcloud.fachstelle.proxy.FachstellenproxyGrpcFachstelleRegistrationRequest;
+import de.ozgcloud.fachstelle.security.User;
+
+@Mapper
+interface FachstelleRegistrationRequestMapper {
+	@Mapping(target = "mukId", source = "id")
+	@Mapping(target = "firmenName", source = "companyName")
+	@Mapping(target = "rechtsform", source = "legalForm")
+	@Mapping(target = "rechtsformText", source = "legalFormText")
+	@Mapping(target = "registerNummer", source = "registerNumber")
+	@Mapping(target = "registerArt", source = "registerType")
+	@Mapping(target = "emailAdresse", source = "emailAddress")
+	@Mapping(target = "anschrift", source = "address")
+	FachstellenproxyGrpcFachstelleRegistrationRequest toFachstelleRegistrationRequest(User user);
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationService.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1e055c06ebedda790acc7317c03df15b8d360822
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationService.java
@@ -0,0 +1,15 @@
+package de.ozgcloud.fachstelle.registration;
+
+import de.ozgcloud.fachstelle.security.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class FachstelleRegistrationService {
+    private final FachstelleRegistrationRemoteService remoteService;
+
+    public boolean register(User user) {
+        return remoteService.register(user);
+    }
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/Registration.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/Registration.java
new file mode 100644
index 0000000000000000000000000000000000000000..01731fdc5616efd2895a42973e5056a4ff9f2833
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/registration/Registration.java
@@ -0,0 +1,5 @@
+package de.ozgcloud.fachstelle.registration;
+
+record Registration(String key) {
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/CurrentUserService.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/CurrentUserService.java
new file mode 100644
index 0000000000000000000000000000000000000000..791fac9aff9c086234c941ec125ccddebba84519
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/CurrentUserService.java
@@ -0,0 +1,40 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.util.Optional;
+
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+import org.springframework.stereotype.Service;
+
+import de.ozgcloud.fachstelle.common.errorhandling.SamlTokenNotFoundException;
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class CurrentUserService {
+
+	private final AuthenticationTrustResolver trustResolver;
+
+	public Authentication getAuthentication() {
+		return findAuthentication().orElseThrow(() -> new IllegalStateException("No authenticated User found"));
+	}
+
+	private Optional<Authentication> findAuthentication() {
+		return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).filter(this::isTrusted);
+	}
+
+	private boolean isTrusted(Authentication authentication) {
+		return !trustResolver.isAnonymous(authentication);
+	}
+
+	public String getSamlToken() {
+		if (getAuthentication() instanceof Saml2Authentication samlAuth) {
+			return samlAuth.getSaml2Response();
+		}
+
+		throw new SamlTokenNotFoundException();
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/DefaultRole.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/DefaultRole.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2b9991e516fb5b6ae333c5cb8467046cee2d148
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/DefaultRole.java
@@ -0,0 +1,14 @@
+package de.ozgcloud.fachstelle.security;
+
+import org.springframework.security.core.GrantedAuthority;
+
+public class DefaultRole implements GrantedAuthority {
+
+	public static final String ROLE = "ROLE_USER";
+
+	@Override
+	public String getAuthority() {
+		return ROLE;
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandler.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..52486d790bc5f31058796415b092246bf755c5bd
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandler.java
@@ -0,0 +1,31 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class FachstelleLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
+
+	private final UserDetailsService userDetailService;
+
+	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+	  throws IOException, ServletException {
+		if (Objects.nonNull(authentication) && authentication.getPrincipal() instanceof User user) {
+			((InMemoryUserDetailService) userDetailService).logout(user);
+		}
+
+		super.handle(request, response, authentication);
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailService.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cc7b980b2fd5af58108d9d5c43d90071e52feb9
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailService.java
@@ -0,0 +1,59 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@Log4j2
+public class InMemoryUserDetailService implements UserDetailsService {
+
+	private final ConcurrentHashMap<String, User> usersMap = new ConcurrentHashMap<>();
+
+	public InMemoryUserDetailService() {
+		LOG.debug("Init InMemoryUserDetailService");
+	}
+
+	public void addUser(final User user) {
+		LOG.debug("Adding user: {}", user);
+		user.setExpirationDate(Instant.now().plus(4, ChronoUnit.HOURS));
+
+		usersMap.put(user.getId(), user);
+
+		LOG.debug("users map: {}", usersMap);
+	}
+
+	void setUser(final User user) {
+		usersMap.put(user.getId(), user);
+	}
+
+	User getUser(String id) {
+		return usersMap.get(id);
+	}
+
+	@Override
+	public User loadUserByUsername(String username) {
+		var optionalEntry = usersMap.entrySet().stream().filter(entry -> username.equals(entry.getValue().getUsername())).findFirst();
+
+		return optionalEntry.map(Map.Entry::getValue).orElseThrow();
+	}
+
+	@Scheduled(fixedRate = 4, timeUnit = TimeUnit.HOURS)
+	void userCleanUp() {
+		var expiredEntries = usersMap.entrySet().stream().filter(entry -> !entry.getValue().isCredentialsNonExpired()).toList();
+		expiredEntries.forEach(expiredEntry -> logout(expiredEntry.getValue()));
+	}
+
+	public void logout(User user) {
+		usersMap.remove(user.getId());
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithm.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithm.java
new file mode 100644
index 0000000000000000000000000000000000000000..292481eaccfd213e0c6f1cd2a34d68f95f93a832
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithm.java
@@ -0,0 +1,41 @@
+package de.ozgcloud.fachstelle.security;
+
+import org.opensaml.xmlsec.algorithm.SignatureAlgorithm;
+
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+
+@NoArgsConstructor
+public final class SHA256withRSAAndMGF1SignatureAlgorithm implements SignatureAlgorithm {
+
+	static final String RSA_ALGORITHM_ID = "RSA";
+	static final String RSA_SHA256_MGF1_ALGORITHM_URL = "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1";
+	static final String RSA_SHA256_MGF1_ALGORITHM_ID = "SHA256withRSAandMGF1";
+	static final String SHA256_ALGORITHM_ID = "SHA-256";
+
+	@NonNull
+	public String getKey() {
+		return RSA_ALGORITHM_ID;
+	}
+
+	@NonNull
+	public String getURI() {
+		return RSA_SHA256_MGF1_ALGORITHM_URL;
+	}
+
+	@NonNull
+	public AlgorithmType getType() {
+		return AlgorithmType.Signature;
+	}
+
+	@NonNull
+	public String getJCAAlgorithmID() {
+		return RSA_SHA256_MGF1_ALGORITHM_ID;
+	}
+
+	@NonNull
+	public String getDigest() {
+		return SHA256_ALGORITHM_ID;
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Decrypter.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Decrypter.java
new file mode 100644
index 0000000000000000000000000000000000000000..172e3bf1a9c7278d57dfdd78a89c7c3b3560c955
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Decrypter.java
@@ -0,0 +1,130 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.Arrays;
+import java.util.List;
+
+import jakarta.annotation.PostConstruct;
+
+import org.opensaml.core.config.InitializationException;
+import org.opensaml.core.config.InitializationService;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
+import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
+import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
+import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.security.converter.RsaKeyConverters;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.core.Saml2X509Credential;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+public class Saml2Decrypter {
+
+	@Getter
+	@Setter
+	private Decrypter decrypter;
+
+	@Value("${spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location}")
+	private Resource decryptionPrivateKeyLocation;
+
+	@Value("${spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location}")
+	private Resource decryptionCertificateLocation;
+
+	private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
+	  Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
+		new SimpleRetrievalMethodEncryptedKeyResolver()));
+
+	@PostConstruct
+	void init() throws InitializationException {
+		InitializationService.initialize();
+
+		var decryptionX509Credential = getDecryptionCredential();
+		var cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey());
+
+		var resolver = new CollectionKeyInfoCredentialResolver(List.of(cred));
+		var setupDecrypter = new Decrypter(null, resolver, encryptedKeyResolver);
+		setupDecrypter.setRootInNewDocument(true);
+
+		decrypter = setupDecrypter;
+	}
+
+	Attribute getDecryptedAttribute(String samlResponse, String attributeName) {
+		var parsedResponse = Saml2Parser.parse(samlResponse);
+
+		decryptResponseElements(parsedResponse);
+
+		return getAttributes(parsedResponse).stream().filter(attribute -> attributeName.equals(attribute.getName())).findFirst().orElseThrow();
+	}
+
+	private void decryptResponseElements(Response response) {
+		response.getEncryptedAssertions().stream()
+		  .map(this::decryptAssertion)
+		  .forEach(assertion -> response.getAssertions().add(assertion));
+	}
+
+	private Assertion decryptAssertion(EncryptedAssertion assertion) {
+		try {
+			return decrypter.decrypt(assertion);
+		} catch (Exception ex) {
+			LOG.error("failed to decrypt assertion {}", assertion);
+			throw new Saml2Exception(ex);
+		}
+	}
+
+	private List<Attribute> getAttributes(Response response) {
+		var attributeStatement = (AttributeStatement) response.getAssertions().getFirst().getStatements().get(1);
+
+		return attributeStatement.getAttributes();
+	}
+
+	private Saml2X509Credential getDecryptionCredential() {
+		var privateKey = readPrivateKey(decryptionPrivateKeyLocation);
+		var certificate = readCertificateFromResource(decryptionCertificateLocation);
+
+		return new Saml2X509Credential(privateKey, certificate, Saml2X509Credential.Saml2X509CredentialType.DECRYPTION);
+	}
+
+	private RSAPrivateKey readPrivateKey(Resource location) {
+		Assert.state(location != null, "No private key location specified");
+		Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist");
+
+		try (var inputStream = location.getInputStream()) {
+			return RsaKeyConverters.pkcs8().convert(inputStream);
+		} catch (IOException e) {
+			throw new IllegalArgumentException(e);
+		}
+	}
+
+	private X509Certificate readCertificateFromResource(Resource location) {
+		Assert.state(location != null, "No certificate location specified");
+		Assert.state(location.exists(), () -> "Certificate  location '" + location + "' does not exist");
+
+		try (var inputStream = location.getInputStream()) {
+			return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream);
+		} catch (IOException | CertificateException e) {
+			throw new IllegalArgumentException(e);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Parser.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Parser.java
new file mode 100644
index 0000000000000000000000000000000000000000..c02667a6c8a935b1d76f74a18aff5cdf9706df3b
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/Saml2Parser.java
@@ -0,0 +1,62 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
+import org.springframework.security.saml2.Saml2Exception;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.xml.BasicParserPool;
+import net.shibboleth.utilities.java.support.xml.ParserPool;
+import net.shibboleth.utilities.java.support.xml.XMLParserException;
+
+@Log4j2
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Saml2Parser {
+
+	static Response parse(String samlResponse) {
+		try {
+			var inputStream = new ByteArrayInputStream(samlResponse.getBytes(StandardCharsets.UTF_8));
+			var document = getParserPool().parse(inputStream);
+
+			return (Response) getResponseUnmarshaller().unmarshall(document.getDocumentElement());
+		} catch (ComponentInitializationException | XMLParserException | UnmarshallingException e) {
+			LOG.error("failed to parse samlResponse {}", samlResponse);
+			throw new Saml2Exception("Failed to parse samlResponse", e);
+		}
+	}
+
+	static ParserPool getParserPool() throws ComponentInitializationException {
+		var parserPool = new BasicParserPool();
+		parserPool.setBuilderFeatures(getXmlFeatureMap());
+		parserPool.setBuilderAttributes(new HashMap<>());
+		parserPool.initialize();
+
+		return parserPool;
+	}
+
+	private static Map<String, Boolean> getXmlFeatureMap() {
+		final Map<String, Boolean> features = new HashMap<>();
+		features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
+		features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
+		features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
+		features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
+		features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
+
+		return features;
+	}
+
+	static ResponseUnmarshaller getResponseUnmarshaller() {
+		return (ResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(Response.DEFAULT_ELEMENT_NAME);
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandler.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f656161a12378385d209e4461bed8ccdb1d53dc
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandler.java
@@ -0,0 +1,30 @@
+package de.ozgcloud.fachstelle.security;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.ModelAndView;
+
+@ControllerAdvice
+public class SecurityExceptionHandler {
+
+	public static final String DEFAULT_ERROR_VIEW = "error";
+
+	@ExceptionHandler(value = SecurityException.class)
+	public ModelAndView handleSecurityException(HttpServletRequest request, SecurityException exception) {
+		ModelAndView mav = new ModelAndView();
+		mav.addObject("exception", exception.getMessage());
+		mav.addObject("url", request.getRequestURL());
+		mav.setViewName(DEFAULT_ERROR_VIEW);
+
+		clearSessionCookie(request);
+
+		return mav;
+	}
+
+	void clearSessionCookie(HttpServletRequest request) {
+		request.getSession().invalidate();
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityProvider.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..93753a6aa460f0c37afa36bc955af3907d4ad560
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/SecurityProvider.java
@@ -0,0 +1,28 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
+import org.springframework.beans.factory.InitializingBean;
+
+public class SecurityProvider implements InitializingBean {
+
+	@Override
+	public void afterPropertiesSet() {
+		Security.addProvider(new BouncyCastleProvider());
+
+		registerSignatureAlgorithms();
+	}
+
+	private void registerSignatureAlgorithms() {
+		var algorithmRegistry = ConfigurationService.get(AlgorithmRegistry.class);
+		if (algorithmRegistry != null) {
+			algorithmRegistry.register(new SHA256withRSAAndMGF1SignatureAlgorithm());
+
+			ConfigurationService.register(AlgorithmRegistry.class, algorithmRegistry);
+		}
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandler.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..09daf3e224312ccc9c3ebc1a5fbf61a2501d5f4b
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandler.java
@@ -0,0 +1,91 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+import org.springframework.security.web.DefaultRedirectStrategy;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.security.web.WebAttributes;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Service
+public class UrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+
+	private final InMemoryUserDetailService userDetailService;
+	private final UserMapper userMapper;
+	private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+	private final Map<String, String> roleTargetUrlMap = new HashMap<>();
+
+	public UrlAuthenticationSuccessHandler(final InMemoryUserDetailService userDetailService, final UserMapper userMapper) {
+		super();
+		this.userDetailService = userDetailService;
+		this.userMapper = userMapper;
+		roleTargetUrlMap.put(DefaultRole.ROLE, "/preregister");
+	}
+
+	@Override
+	public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication)
+	  throws IOException {
+		handle(request, response, authentication);
+		if (authentication instanceof Saml2Authentication saml2Authentication) {
+			LOG.debug("Adding User with SamlResponse: {}", saml2Authentication.getSaml2Response());
+			var user = userMapper.map(saml2Authentication);
+			LOG.info("Mapped SamlResponse to user: {}", user);
+			userDetailService.addUser(user);
+		} else {
+			throw new SecurityException("Unsupported authentication type");
+		}
+		clearAuthenticationAttributes(request);
+	}
+
+	protected void handle(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication)
+	  throws IOException {
+		final String targetUrl = determineTargetUrl(authentication);
+
+		if (response.isCommitted()) {
+			LOG.debug("Response has already been committed. Unable to redirect to {}", targetUrl);
+			return;
+		}
+
+		redirectStrategy.sendRedirect(request, response, targetUrl);
+	}
+
+	protected String determineTargetUrl(final Authentication authentication) {
+		final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+		for (final GrantedAuthority grantedAuthority : authorities) {
+			String authorityName = grantedAuthority.getAuthority();
+			if (roleTargetUrlMap.containsKey(authorityName)) {
+				return roleTargetUrlMap.get(authorityName);
+			}
+		}
+
+		throw new IllegalStateException("Invalid role! User is missing role " + DefaultRole.ROLE);
+	}
+
+	/**
+	 * Removes temporary authentication-related data which may have been stored in the session during the authentication process.
+	 */
+	protected final void clearAuthenticationAttributes(final HttpServletRequest request) {
+		final HttpSession session = request.getSession(false);
+
+		if (session == null) {
+			return;
+		}
+
+		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/User.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/User.java
new file mode 100644
index 0000000000000000000000000000000000000000..834fefa3aee717cfc7b813630ad6c01e89172f7c
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/User.java
@@ -0,0 +1,71 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Builder(toBuilder = true)
+@Getter
+@ToString
+public class User implements UserDetails {
+
+	private String id;
+	private String username;
+	private String companyName;
+	private String legalForm;
+	private String legalFormText;
+	private String registerNumber;
+	private String registerType;
+	private String emailAddress;
+	private String address;
+	private String password;
+	private String trustLevel;
+	@Setter
+	private String registrationKey;
+	@Setter
+	private Instant registrationKeyExpiresAt;
+	@Setter(AccessLevel.PACKAGE)
+	private Instant expirationDate;
+	private transient List<Map.Entry<String, List<Object>>> unknownAttributes;
+
+	@Override
+	public Collection<? extends GrantedAuthority> getAuthorities() {
+		return List.of(new DefaultRole());
+	}
+
+	@Override
+	public String getPassword() {
+		return password;
+	}
+
+	@Override
+	public boolean isAccountNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isAccountNonLocked() {
+		return true;
+	}
+
+	@Override
+	public boolean isCredentialsNonExpired() {
+		return expirationDate != null && expirationDate.isAfter(Instant.now());
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return true;
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserAttributeProvider.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserAttributeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..8dcc91914522f3a324362d0fa2d5b91570d98700
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserAttributeProvider.java
@@ -0,0 +1,106 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.opensaml.core.xml.XMLObject;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
+import org.springframework.stereotype.Component;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Component
+@RequiredArgsConstructor
+class UserAttributeProvider {
+
+	static final String SAML_XML_STRASSE_NODE_NAME = "Strasse";
+	static final String SAML_XML_HAUSNUMMER_NODE_NAME = "Hausnummer";
+	static final String SAML_XML_PLZ_NODE_NAME = "PLZ";
+	static final String SAML_XML_ORT_NODE_NAME = "Ort";
+	static final String SAML_XML_LAND_NODE_NAME = "Land";
+
+	static final String MUK_FIRMENNAME_KEY = "Firmenname";
+	static final String MUK_RECHTSFORM_KEY = "Rechtsform";
+	static final String MUK_RECHTSFORM_TEXT_KEY = "RechtsformText";
+	static final String MUK_REGISTERNUMMER_KEY = "Registernummer";
+	static final String MUK_REGISTERART_KEY = "Registerart";
+	static final String MUK_EMAIL_ADRESSE_KEY = "EMailAdresse";
+	static final String MUK_ADRESSE_KEY = "Unternehmensanschrift";
+	static final String MUK_VERTRAUENSNIVEAU_KEY = "ElsterVertrauensniveauAuthentifizierung";
+	private static final String[] MUK_KNOWN_ATTRIBUTES = new String[] { MUK_FIRMENNAME_KEY, MUK_RECHTSFORM_KEY, MUK_RECHTSFORM_TEXT_KEY,
+	  MUK_REGISTERNUMMER_KEY, MUK_REGISTERART_KEY, MUK_EMAIL_ADRESSE_KEY, MUK_ADRESSE_KEY, MUK_VERTRAUENSNIVEAU_KEY };
+
+	private final Saml2Decrypter saml2Decrypter;
+
+	String getCompanyName(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_FIRMENNAME_KEY);
+	}
+
+	String getLegalForm(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_RECHTSFORM_KEY);
+	}
+
+	String getLegalFormText(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_RECHTSFORM_TEXT_KEY);
+	}
+
+	String getRegisterNumber(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_REGISTERNUMMER_KEY);
+	}
+
+	String getRegisterType(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_REGISTERART_KEY);
+	}
+
+	String getEmailAddress(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_EMAIL_ADRESSE_KEY);
+	}
+
+	String getTrustLevel(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getFirstAttribute(MUK_VERTRAUENSNIVEAU_KEY);
+	}
+
+	String getAddress(String samlResponse) {
+		try {
+			var addressNode = saml2Decrypter.getDecryptedAttribute(samlResponse, MUK_ADRESSE_KEY);
+			var addressPartNodes = addressNode.getAttributeValues().getFirst().getOrderedChildren();
+
+			if (addressPartNodes != null && !addressPartNodes.isEmpty()) {
+				return getAddressByXMLNodes(addressPartNodes);
+			}
+		} catch (IllegalArgumentException | Saml2Exception | NoSuchElementException e) {
+			LOG.error("Failed parsing company address from SamlResponse: {}", samlResponse);
+		}
+
+		return null;
+	}
+
+	private static String getAddressByXMLNodes(final List<XMLObject> addressPartNodes) {
+		var addressBuilder = new StringBuilder();
+
+		for (XMLObject node : addressPartNodes) {
+			var nodeName = node.getElementQName().getLocalPart();
+			var textContent = Objects.requireNonNull(node.getDOM()).getTextContent().trim();
+
+			addressBuilder.append(textContent);
+			if (SAML_XML_STRASSE_NODE_NAME.equals(nodeName) || SAML_XML_PLZ_NODE_NAME.equals(nodeName)) {
+				addressBuilder.append(" ");
+			} else if (SAML_XML_HAUSNUMMER_NODE_NAME.equals(nodeName) || SAML_XML_ORT_NODE_NAME.equals(nodeName)) {
+				addressBuilder.append(", ");
+			}
+		}
+
+		return addressBuilder.toString().trim();
+	}
+
+	List<Map.Entry<String, List<Object>>> getUnknownAttributes(DefaultSaml2AuthenticatedPrincipal principal) {
+		return principal.getAttributes().entrySet().stream().filter(entry -> !ArrayUtils.contains(MUK_KNOWN_ATTRIBUTES, entry.getKey())).toList();
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserMapper.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d80722847c5d43dd5f6d1a9d8d35269227199d0
--- /dev/null
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/security/UserMapper.java
@@ -0,0 +1,33 @@
+package de.ozgcloud.fachstelle.security;
+
+import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+import org.springframework.stereotype.Component;
+
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+public class UserMapper {
+
+	private final UserAttributeProvider userAttributeProvider;
+
+	User map(Saml2Authentication authentication) {
+		var principal = (DefaultSaml2AuthenticatedPrincipal) authentication.getPrincipal();
+
+		return new User.UserBuilder()
+		  .id(principal.getName())
+		  .username(principal.getName())
+		  .companyName(userAttributeProvider.getCompanyName(principal))
+		  .legalForm(userAttributeProvider.getLegalForm(principal))
+		  .legalFormText(userAttributeProvider.getLegalFormText(principal))
+		  .registerNumber(userAttributeProvider.getRegisterNumber(principal))
+		  .registerType(userAttributeProvider.getRegisterType(principal))
+		  .emailAddress(userAttributeProvider.getEmailAddress(principal))
+		  .address(userAttributeProvider.getAddress(authentication.getSaml2Response()))
+		  .trustLevel(userAttributeProvider.getTrustLevel(principal))
+		  .unknownAttributes(userAttributeProvider.getUnknownAttributes(principal))
+		  .build();
+	}
+
+}
diff --git a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteService.java b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteService.java
index 4ba5760478249e140dda96b02534ce06e58cb38b..c7c9436971548a8d71f9a5e01a4c990171d11059 100644
--- a/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteService.java
+++ b/fachstelle-server/src/main/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteService.java
@@ -38,33 +38,34 @@ import lombok.extern.log4j.Log4j2;
 @Component
 class VorgangRemoteService {
 
-	public static final String DUMMY_SAML_TOKEN = "--dummy-saml-token--";
-	static final String FIND_VORGANG_URI = "/api/vorgang/{vorgangId}/{samlToken}";
-	private final RestClient fachstellenProxyClient;
-	private final FachstellenProxyProperties fachstellenProxyProperties;
-	private final VorgangMapper vorgangMapper;
+    public static final String DUMMY_SAML_TOKEN = "--dummy-saml-token--";
+    static final String FIND_VORGANG_URI = "/api/vorgang/{vorgangId}/{samlToken}";
 
-	Vorgang findVorgang(String id, String collaborationManagerAddress) {
-		var response = fachstellenProxyClient.get().uri(buildFindVorgangUri(id))
-		  .accept(MediaType.APPLICATION_JSON)
-		  .header(fachstellenProxyProperties.getCollaborationManagerAddressHeader(), collaborationManagerAddress)
-		  .retrieve()
-		  .toEntity(CollaborationGrpcFindVorgangResponse.class)
-		  .getBody();
+    private final RestClient fachstellenProxyClient;
+    private final FachstellenProxyProperties fachstellenProxyProperties;
+    private final VorgangMapper vorgangMapper;
 
-		if (response == null) {
-			throw new IllegalStateException("Received null vorgang response");
-		}
+    Vorgang findVorgang(String id, String collaborationManagerAddress) {
+        var response = fachstellenProxyClient.get().uri(buildFindVorgangUri(id))
+                .accept(MediaType.APPLICATION_JSON)
+                .header(fachstellenProxyProperties.getCollaborationManagerAddressHeader(), collaborationManagerAddress)
+                .retrieve()
+                .toEntity(CollaborationGrpcFindVorgangResponse.class)
+                .getBody();
 
-		return buildVorgangResponse(response);
-	}
+        if (response == null) {
+            throw new IllegalStateException("Received null vorgang response");
+        }
 
-	String buildFindVorgangUri(String vorgangId) {
-		return new UriTemplate(FIND_VORGANG_URI).expand(vorgangId, DUMMY_SAML_TOKEN).toString();
-	}
+        return buildVorgangResponse(response);
+    }
 
-	private Vorgang buildVorgangResponse(CollaborationGrpcFindVorgangResponse response) {
-		return vorgangMapper.toVorgang(response.getVorgang());
-	}
+    String buildFindVorgangUri(String vorgangId) {
+        return new UriTemplate(FIND_VORGANG_URI).expand(vorgangId, DUMMY_SAML_TOKEN).toString();
+    }
+
+    private Vorgang buildVorgangResponse(CollaborationGrpcFindVorgangResponse response) {
+        return vorgangMapper.toVorgang(response.getVorgang());
+    }
 
 }
diff --git a/fachstelle-server/src/main/resources/application.yml b/fachstelle-server/src/main/resources/application.yml
index 215712d6a66aa7dedaced163341a5dcabe7f94ad..e5b2415565bfce8b9352f1318f623f39f6ba35a8 100644
--- a/fachstelle-server/src/main/resources/application.yml
+++ b/fachstelle-server/src/main/resources/application.yml
@@ -27,6 +27,11 @@ spring:
   application:
     name: Fachstellenbeteiligung
   security:
+    oauth2:
+      resourceserver:
+        jwt:
+          issuer-uri: ${ozgcloud.oauth2.issuer-uri}
+          jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
     saml2:
       relyingparty:
         registration:
@@ -35,6 +40,8 @@ spring:
             assertingparty:
               singlesignon:
                 sign-request: true
+  mvc:
+    static-path-pattern: /static/**
 
 logging:
   level:
@@ -47,7 +54,8 @@ logging:
 ozgcloud:
   oauth2:
     auth-server-url: ${keycloak.auth-server-url}
-    realm: ${keycloak.realm}
+    realm: fachstelle
     resource: ${keycloak.resource}
+    issuer-uri: ${ozgcloud.oauth2.auth-server-url}/realms/${ozgcloud.oauth2.realm}
   fachstellen-proxy:
     collaboration-manager-address-header: X-Grpc-Address
\ No newline at end of file
diff --git a/fachstelle-server/src/main/resources/static/index.html b/fachstelle-server/src/main/resources/static/index.html
index 5aa76a97c1b523cc6083fe32bb430631939f6fd1..39c3f85470782463286b60a969d9357035ded278 100644
--- a/fachstelle-server/src/main/resources/static/index.html
+++ b/fachstelle-server/src/main/resources/static/index.html
@@ -24,23 +24,24 @@
 <html lang="de">
 <head>
     <meta charset="utf-8"/>
-    <link href="/favicon.ico" rel="icon"/>
+    <link href="static/favicon.ico" rel="icon"/>
     <meta content="width=device-width, initial-scale=1" name="viewport"/>
     <meta content="#000000" name="theme-color"/>
     <meta
             content="Seite zur Registrierung als Fachstelle auf Nachfrage einer Behörde"
             name="description"
     />
-    <link href="App.css" rel="stylesheet"/>
+    <link href="static/App.css" rel="stylesheet"/>
     <link href="webjars/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet"/>
 
     <title>Fachstellen Registrierung</title>
 </head>
 <body>
+<h1>debug</h1>
 <noscript>You need to enable JavaScript to run this app.</noscript>
 <div class="container content-box">
     <div class="container App-header" id="ozg-header">
-        <img alt="ozg cloud logo" class="App-logo" src="ozg_cloud_logo.png"/>
+        <img alt="ozg cloud logo" class="App-logo" src="static/ozg_cloud_logo.png"/>
         <span id="app-text">Fachstellenregistrierung</span>
         <hr/>
     </div>
diff --git a/fachstelle-server/src/main/resources/templates/fragments/header.html b/fachstelle-server/src/main/resources/templates/fragments/header.html
index ae9615c95c2bf78c492b2cf014a97e784ca600a7..94e3b04981376cd5c47e4bea555b3b73ab480089 100644
--- a/fachstelle-server/src/main/resources/templates/fragments/header.html
+++ b/fachstelle-server/src/main/resources/templates/fragments/header.html
@@ -1,18 +1,18 @@
 <head>
     <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
-    <link href="favicon.ico" rel="icon"/>
+    <link href="static/favicon.ico" rel="icon"/>
     <meta content="width=device-width, initial-scale=1" name="viewport"/>
     <meta content="#000000" name="theme-color"/>
     <meta content="Seite zur Registrierung als Fachstelle auf Nachfrage einer Behörde" name="description"/>
-    <link href="ozg_cloud_logo.png" rel="apple-touch-icon"/>
-    <link href="App.css" rel="stylesheet"/>
+    <link href="static/ozg_cloud_logo.png" rel="apple-touch-icon"/>
+    <link href="static/App.css" rel="stylesheet"/>
     <link href="webjars/bootstrap/5.3.3/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
 
     <title>Fachstellen Registrierung</title>
 </head>
 
 <div class="container App-header" id="ozg-header">
-    <img alt="ozg cloud logo" class="App-logo" id="app-logo" src="ozg_cloud_logo.png"/>
+    <img alt="ozg cloud logo" class="App-logo" id="app-logo" src="static/ozg_cloud_logo.png"/>
     <span class="sub-title-text" id="app-text" th:text="#{messages.fachstellenregistrierung}">
         Fachstellenregistrierung
     </span>
diff --git a/fachstelle-server/src/test/helm/ingress_test.yaml b/fachstelle-server/src/test/helm/ingress_test.yaml
index 28fb86bc74490570e3c95d8895540672a61dd7a9..5bbfb6c24feae31c85cc0d449c9790201fba976e 100644
--- a/fachstelle-server/src/test/helm/ingress_test.yaml
+++ b/fachstelle-server/src/test/helm/ingress_test.yaml
@@ -114,6 +114,66 @@ tests:
                     name: fachstelle-server
                     port: 
                       number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /registrierung
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /saml2
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /login
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /preregister
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /register
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
+      - contains:
+            path: spec.rules[0].http.paths
+            content:
+                path: /success
+                pathType: Prefix
+                backend:
+                  service:
+                    name: fachstelle-server
+                    port:
+                      number: 8080
 
   - it: should fail template when baseUrl not set
     set:
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/SecurityConfigurationTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/SecurityConfigurationTest.java
index cdeb76e4d55240ef71010ff26ed53915235f3a78..078190301fdcc0d63d7107158d420619991b4800 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/SecurityConfigurationTest.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/SecurityConfigurationTest.java
@@ -23,103 +23,151 @@
  */
 package de.ozgcloud.fachstelle;
 
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
+import de.ozgcloud.fachstelle.security.FachstelleLogoutSuccessHandler;
+import de.ozgcloud.fachstelle.security.InMemoryUserDetailService;
+import de.ozgcloud.fachstelle.security.UrlAuthenticationSuccessHandler;
 
 @ExtendWith(MockitoExtension.class)
 class SecurityConfigurationTest {
 
-    @Mock
-    FachstellenProperties fachstellenProperties;
+	@InjectMocks
+	private SecurityConfiguration securityConfiguration;
+
+	@Mock
+	private InMemoryUserDetailService userDetailsService;
+	@Mock
+	private UrlAuthenticationSuccessHandler urlAuthenticationSuccessHandler;
+
+	@Mock
+	private FachstellenProperties fachstellenProperties;
+	@Mock
+	private SpringJwtProperties springJwtProperties;
+
+	@Test
+	void shouldCreateSecurityProvider() {
+		var securityProvider = securityConfiguration.securityProvider();
+
+		assertThat(securityProvider).isNotNull();
+	}
+
+	@Test
+	void shouldCreateAuthenticationTrustResolver() {
+		var authenticationTrustResolver = securityConfiguration.authenticationTrustResolver();
+
+		assertThat(authenticationTrustResolver).isNotNull();
+	}
+
+	@Nested
+	class TestHttpSecurity {
+
+		private HttpSecurity httpSecurity;
+
+		@BeforeEach
+		void init() throws Exception {
+			httpSecurity = mock(HttpSecurity.class);
+			when(httpSecurity.authorizeHttpRequests(any())).thenReturn(httpSecurity);
+			when(httpSecurity.saml2Login(any())).thenReturn(httpSecurity);
+			when(httpSecurity.saml2Logout(any())).thenReturn(httpSecurity);
+		}
+
+		@Test
+		void shouldSetupFilterChainAuthorize() throws Exception {
+			securityConfiguration.filterChain(httpSecurity);
+
+			verify(httpSecurity).authorizeHttpRequests(any());
+		}
+
+		@Disabled
+		@Test
+		void shouldSetupFilterChainSaml2Login() throws Exception {
+			securityConfiguration.filterChain(httpSecurity);
 
-    private SecurityConfiguration securityConfiguration;
+			verify(httpSecurity).saml2Login(any());
+		}
 
-    @BeforeEach
-    void setUp() {
-        securityConfiguration = new SecurityConfiguration(fachstellenProperties);
-    }
+		@Disabled
+		@Test
+		void shouldSetupFilterChainSaml2Logout() throws Exception {
+			securityConfiguration.filterChain(httpSecurity);
 
-    @Test
-    void shouldCreateAuthenticationTrustResolver() {
-        var authenticationTrustResolver = securityConfiguration.authenticationTrustResolver();
+			verify(httpSecurity).saml2Logout(any());
+		}
 
-        assertThat(authenticationTrustResolver).isNotNull();
-    }
+	}
 
-    @Nested
-    class TestHttpSecurity {
+	@Nested
+	class TestAuthenticationRequestResolver {
 
-        private HttpSecurity httpSecurity;
+		@Mock
+		private RelyingPartyRegistrationRepository registrations;
 
-        @BeforeEach
-        void init() throws Exception {
-            httpSecurity = mock(HttpSecurity.class);
-            when(httpSecurity.authorizeHttpRequests(any())).thenReturn(httpSecurity);
-        }
+		@Test
+		void shouldCreateSaml2AuthenticationRequestResolver() {
+			var authResolver = securityConfiguration.authenticationRequestResolver(registrations);
 
-        @Test
-        void shouldSetupFilterChainAuthorize() throws Exception {
-            securityConfiguration.filterChain(httpSecurity);
+			assertThat(authResolver).isInstanceOf(Saml2AuthenticationRequestResolver.class);
+		}
 
-            verify(httpSecurity).authorizeHttpRequests(any());
-        }
+	}
 
-        @Disabled
-        @Test
-        void shouldSetupFilterChainSaml2Login() throws Exception {
-            securityConfiguration.filterChain(httpSecurity);
+	@Nested
+	class TestSamlFilterChain {
 
-            verify(httpSecurity).saml2Login(any());
-        }
+		@Mock
+		HttpSecurity security;
 
-        @Disabled
-        @Test
-        void shouldSetupFilterChainSaml2Logout() throws Exception {
-            securityConfiguration.filterChain(httpSecurity);
+		@BeforeEach
+		void setUp() throws Exception {
+			when(security.authorizeHttpRequests(any())).thenReturn(security);
+			when(security.saml2Login(any())).thenReturn(security);
+			when(security.saml2Logout(any())).thenReturn(security);
+		}
 
-            verify(httpSecurity).saml2Logout(any());
-        }
+		@Disabled
+		@Test
+		void shouldSetSamlLogin() throws Exception {
+			securityConfiguration.filterChain(security);
 
-    }
+			verify(security).saml2Login(any());
+		}
 
-    @Nested
-    class TestSamlFilterChain {
+		@Disabled
+		@Test
+		void shouldSetSamlLogout() throws Exception {
+			securityConfiguration.filterChain(security);
 
-        @Mock
-        HttpSecurity security;
+			verify(security).saml2Logout(any());
+		}
 
-        @BeforeEach
-        void setUp() throws Exception {
-            when(security.authorizeHttpRequests(any())).thenReturn(security);
-            when(security.saml2Login(any())).thenReturn(security);
-            when(security.saml2Logout(any())).thenReturn(security);
-        }
+	}
 
-        @Disabled
-        @Test
-        void shouldSetSamlLogin() throws Exception {
-            securityConfiguration.filterChain(security);
+	@Nested
+	class TestLogoutHandler {
 
-            verify(security).saml2Login(any());
-        }
+		@Test
+		void shouldCreateLogoutHandler() {
+			when(fachstellenProperties.getLogoutSuccessUrl()).thenReturn("http://localhost:8080/logout");
 
-        @Disabled
-        @Test
-        void shouldSetSamlLogout() throws Exception {
-            securityConfiguration.filterChain(security);
+			var handler = securityConfiguration.getLogoutSuccessHandler();
 
-            verify(security).saml2Logout(any());
-        }
+			assertThat(handler).isInstanceOf(FachstelleLogoutSuccessHandler.class);
+		}
 
-    }
+	}
 
 }
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/attachment/AttachmentControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/attachment/AttachmentControllerITCase.java
index 5203d17fc9f73e8ebbe1810dc882ad689043cd8a..7509e4a54febda1cff20a738119affdba0740734 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/attachment/AttachmentControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/attachment/AttachmentControllerITCase.java
@@ -47,17 +47,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class AttachmentControllerITCase {
 
@@ -115,9 +117,9 @@ class AttachmentControllerITCase {
 	@SneakyThrows
 	private ResultActions performRequest() {
 		return mockMvc.perform(
-		  get(AttachmentController.PATH + "?eingangId=" + EingangTestFactory.ID)
-			.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-			.with(csrf().asHeader()));
+				get(AttachmentController.PATH + "?eingangId=" + EingangTestFactory.ID)
+						.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+						.with(csrf().asHeader()));
 	}
 
 }
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/command/CommandControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/command/CommandControllerITCase.java
index 4dfb10ec6a529b8f3180f20aa547c567b97554ba..e14b64142d4da0845852bef0470cf21e1d16c71d 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/command/CommandControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/command/CommandControllerITCase.java
@@ -46,81 +46,83 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+        "ozgcloud.fachstelle.logout-success-url=http://logout",
+        "ozgcloud.fachstelle.login-redirect-url=http://login",
+        "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+        "ozgcloud.fachstellen-proxy.base-url=http://proxy",
+        "keycloak.auth-server-url=http://keycloak",
+        "keycloak.realm=fachstelle",
+        "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+        "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+        "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+        "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+        "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+        "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+        "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class CommandControllerITCase {
 
-	@Autowired
-	private MockMvc mockMvc;
-
-	@SneakyThrows
-	@Test
-	void shouldReturnStatusOk() {
-		performRequest().andExpect(status().isOk());
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveId() {
-		performRequest().andExpect(jsonPath("$.id").value(CommandTestFactory.ID));
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveCreatedAt() {
-		performRequest().andExpect(jsonPath("$.createdAt").exists());
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveFinishedAt() {
-		performRequest().andExpect(jsonPath("$.finishedAt").exists());
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveStatus() {
-		performRequest().andExpect(jsonPath("$.status").value(CommandStatus.FINISHED.toString()));
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveVorgangId() {
-		performRequest().andExpect(jsonPath("$.vorgangId").value("TestVorgangId"));
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveOrder() {
-		performRequest().andExpect(jsonPath("$.order").value("TestOrder"));
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveErrorMessage() {
-		performRequest().andExpect(jsonPath("$.errorMessage").value(""));
-	}
-
-	@SneakyThrows
-	@Test
-	void shouldHaveLinks() {
-		performRequest().andExpect(jsonPath("$._links").exists());
-	}
-
-	@SneakyThrows
-	private ResultActions performRequest() {
-		return mockMvc.perform(get(CommandController.PATH + "/" + CommandTestFactory.ID).contentType(MediaType.APPLICATION_JSON)
-		  .characterEncoding(Charset.defaultCharset()).with(csrf().asHeader()));
-	}
+    @Autowired
+    private MockMvc mockMvc;
+
+    @SneakyThrows
+    @Test
+    void shouldReturnStatusOk() {
+        performRequest().andExpect(status().isOk());
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveId() {
+        performRequest().andExpect(jsonPath("$.id").value(CommandTestFactory.ID));
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveCreatedAt() {
+        performRequest().andExpect(jsonPath("$.createdAt").exists());
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveFinishedAt() {
+        performRequest().andExpect(jsonPath("$.finishedAt").exists());
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveStatus() {
+        performRequest().andExpect(jsonPath("$.status").value(CommandStatus.FINISHED.toString()));
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveVorgangId() {
+        performRequest().andExpect(jsonPath("$.vorgangId").value("TestVorgangId"));
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveOrder() {
+        performRequest().andExpect(jsonPath("$.order").value("TestOrder"));
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveErrorMessage() {
+        performRequest().andExpect(jsonPath("$.errorMessage").value(""));
+    }
+
+    @SneakyThrows
+    @Test
+    void shouldHaveLinks() {
+        performRequest().andExpect(jsonPath("$._links").exists());
+    }
+
+    @SneakyThrows
+    private ResultActions performRequest() {
+        return mockMvc.perform(get(CommandController.PATH + "/" + CommandTestFactory.ID).contentType(MediaType.APPLICATION_JSON)
+                .characterEncoding(Charset.defaultCharset()).with(csrf().asHeader()));
+    }
 
 }
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/historie/HistorieControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/historie/HistorieControllerITCase.java
index 9a1d874491becce6a2bd6f5e8bf8c11ff758b6ac..cefc03c6e56df87170e7e2afacccf28f20bc8ed3 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/historie/HistorieControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/historie/HistorieControllerITCase.java
@@ -48,17 +48,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class HistorieControllerITCase {
 
@@ -134,9 +136,9 @@ class HistorieControllerITCase {
 	@SneakyThrows
 	private ResultActions performRequest() {
 		return mockMvc.perform(
-		  get(HistorieController.PATH + "?vorgangId=" + VorgangTestFactory.ID)
-			.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-			.with(csrf().asHeader()));
+				get(HistorieController.PATH + "?vorgangId=" + VorgangTestFactory.ID)
+						.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+						.with(csrf().asHeader()));
 	}
 
 }
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarByVorgangControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarByVorgangControllerITCase.java
index a32e9fd3c14285788140e01395f2442653abb4bb..649b932a2461ab8b28fe77aadc9f71c7f01e7df1 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarByVorgangControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarByVorgangControllerITCase.java
@@ -47,17 +47,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class KommentarByVorgangControllerITCase {
 
@@ -133,9 +135,9 @@ class KommentarByVorgangControllerITCase {
 	@SneakyThrows
 	private ResultActions performRequest() {
 		return mockMvc.perform(
-		  get(KommentarController.KommentarByVorgangController.PATH + "/" + VorgangTestFactory.ID + "/kommentars")
-			.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-			.with(csrf().asHeader()));
+				get(KommentarController.KommentarByVorgangController.PATH + "/" + VorgangTestFactory.ID + "/kommentars")
+						.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+						.with(csrf().asHeader()));
 	}
 
 }
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarControllerITCase.java
index 9598b4787caabd9fc9e9d8a619c716d02569f35e..d79f9f01334e2b0aa90a0627489675cfb3e67e19 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/kommentar/KommentarControllerITCase.java
@@ -47,17 +47,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class KommentarControllerITCase {
 
@@ -124,9 +126,9 @@ class KommentarControllerITCase {
 		@SneakyThrows
 		private ResultActions performRequest() {
 			return mockMvc.perform(
-			  get(KommentarController.PATH + "/" + KommentarTestFactory.ID)
-				.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-				.with(csrf().asHeader()));
+					get(KommentarController.PATH + "/" + KommentarTestFactory.ID)
+							.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+							.with(csrf().asHeader()));
 		}
 
 	}
@@ -185,9 +187,9 @@ class KommentarControllerITCase {
 		@SneakyThrows
 		private ResultActions performRequest() {
 			return mockMvc.perform(
-			  get(KommentarController.PATH + "/" + KommentarTestFactory.ID + "/attachments")
-				.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-				.with(csrf().asHeader()));
+					get(KommentarController.PATH + "/" + KommentarTestFactory.ID + "/attachments")
+							.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+							.with(csrf().asHeader()));
 		}
 
 	}
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerITCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..66fde07fcec09f51ae36dd62fecfaf5b40e570a2
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerITCase.java
@@ -0,0 +1,47 @@
+package de.ozgcloud.fachstelle.registration;
+
+import static org.hamcrest.Matchers.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc(printOnlyOnFailure = false)
+@TestPropertySource(properties = {
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+})
+class FachstelleRegistrationControllerITCase {
+
+	@Autowired
+	private MockMvc mockMvc;
+
+	@Test
+	void whenCallIndexPage_ThenReturnPage() throws Exception {
+		mockMvc.perform(MockMvcRequestBuilders.get("/registrierung")
+						.header("Accept-Language", "de"))
+				.andExpect(status().isOk())
+				.andExpect(content().string(containsString("Registrierung als Fachstelle")));
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce2a95e161c95668f0e243cb6d731f8fefd614a2
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationControllerTest.java
@@ -0,0 +1,356 @@
+package de.ozgcloud.fachstelle.registration;
+
+import static de.ozgcloud.fachstelle.security.UserTestFactory.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.ui.Model;
+
+import de.ozgcloud.fachstelle.FachstellenProperties;
+import de.ozgcloud.fachstelle.security.InMemoryUserDetailService;
+import de.ozgcloud.fachstelle.security.User;
+
+@ExtendWith(MockitoExtension.class)
+class FachstelleRegistrationControllerTest {
+
+	@InjectMocks
+	@Spy
+	private FachstelleRegistrationController fachstelleRegistrationController;
+	@Mock
+	private InMemoryUserDetailService userDetailService;
+	@Mock
+	private FachstellenProperties fachstellenProperties;
+	@Mock
+	private FachstelleRegistrationService registrationService;
+
+	@Nested
+	class TestStartRegistration {
+
+		@Mock
+		private Model model;
+
+		@BeforeEach
+		void setUp() {
+			when(fachstellenProperties.getLoginRedirectUrl()).thenReturn("/login");
+		}
+
+		@Test
+		void shouldCreateIndexPage() {
+			var res = fachstelleRegistrationController.startRegistration(model);
+
+			assertThat(res).isEqualTo("index");
+		}
+
+		@Test
+		void shouldSetLoginUrl() {
+			fachstelleRegistrationController.startRegistration(model);
+
+			verify(model).addAttribute("loginUrl", "/login");
+		}
+
+	}
+
+	@Nested
+	class TestRegistrationPage {
+
+		@Mock
+		private Model model;
+
+		@Mock
+		private Authentication authentication;
+
+		@BeforeEach
+		void setUp() {
+			when(authentication.getName()).thenReturn("user");
+			when(userDetailService.loadUserByUsername("user")).thenReturn(create());
+		}
+
+		@Test
+		void shouldCreateRegisterPage() {
+			var res = fachstelleRegistrationController.preregister(model, authentication);
+
+			assertThat(res).isEqualTo("preregister");
+		}
+
+		@Test
+		void shouldSetOrganizationName() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationName", COMPANY_NAME);
+		}
+
+		@Test
+		void shouldSetOrganizationLegalForm() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationLegalForm", LEGAL_FORM_TEXT);
+		}
+
+		@Test
+		void shouldSetOrganizationRegisterType() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationRegisterType", REGISTER_TYPE);
+		}
+
+		@Test
+		void shouldSetOrganizationRegisterNumber() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationRegisterNumber", REGISTER_NUMBER);
+		}
+
+		@Test
+		void shouldSetOrganizationEmail() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationEmail", EMAIL_ADDRESS);
+		}
+
+		@Test
+		void shouldSetOrganizationAddress() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute("organizationAddress", ADDRESS);
+		}
+
+		@Test
+		void shouldSetRegistration() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(model).addAttribute(eq("registration"), any(Registration.class));
+		}
+
+		@Test
+		void shouldSetRegistrationKey() {
+			fachstelleRegistrationController.preregister(model, authentication);
+
+			verify(fachstelleRegistrationController).addRegistrationKey(any(User.class));
+		}
+
+	}
+
+	@Nested
+	class TestSettingRegistrationKey {
+
+		User user = create();
+
+		@Test
+		void shouldSetRegistrationKey() {
+			fachstelleRegistrationController.addRegistrationKey(user);
+
+			assertThat(user.getRegistrationKey()).isNotNull();
+		}
+
+		@Test
+		void shouldSetRegistrationExpirationDate() {
+			fachstelleRegistrationController.addRegistrationKey(user);
+
+			assertThat(user.getRegistrationKeyExpiresAt()).isBetween(Instant.now().plus(598, ChronoUnit.SECONDS),
+			  Instant.now().plus(602, ChronoUnit.SECONDS));
+		}
+
+	}
+
+	@Nested
+	class TestSuccessPage {
+
+		@Mock
+		private Model model;
+
+		@Mock
+		private Authentication authentication;
+
+		@Mock
+		private HttpServletRequest request;
+
+		@Mock
+		private HttpSession session;
+
+		@BeforeEach
+		void setUp() {
+			when(authentication.getName()).thenReturn("user");
+			when(userDetailService.loadUserByUsername("user")).thenReturn(create());
+			when(request.getSession()).thenReturn(session);
+		}
+
+		@Test
+		void shouldCreateSuccessPage() {
+			var res = fachstelleRegistrationController.success(model, authentication, request);
+
+			assertThat(res).isEqualTo("success");
+		}
+
+		@Test
+		void shouldSetUserName() {
+			fachstelleRegistrationController.success(model, authentication, request);
+
+			verify(model).addAttribute(eq("name"), any(User.class));
+		}
+
+		@Test
+		void shouldInvalidateSession() {
+			fachstelleRegistrationController.success(model, authentication, request);
+
+			verify(session).invalidate();
+		}
+
+		@Test
+		void shouldLogout() {
+			fachstelleRegistrationController.success(model, authentication, request);
+
+			verify(userDetailService).logout(any(User.class));
+		}
+
+	}
+
+	@Nested
+	class TestRegistration {
+
+		@Mock
+		private Model model;
+
+		@Mock
+		private Authentication authentication;
+
+		@Mock
+		private Registration registration;
+
+		@BeforeEach
+		void setUp() {
+			when(authentication.getName()).thenReturn("user");
+			var user = create();
+			user.setRegistrationKey("key");
+			user.setRegistrationKeyExpiresAt(Instant.now().plus(10, ChronoUnit.MINUTES));
+			when(userDetailService.loadUserByUsername("user")).thenReturn(user);
+			when(registration.key()).thenReturn("key");
+		}
+
+		@Test
+		void shouldRegister() {
+			fachstelleRegistrationController.register(registration, model, authentication);
+
+			verify(registrationService).register(any(User.class));
+		}
+
+		@Test
+		void shouldReturnSuccessOnRegistration() {
+			when(registrationService.register(any(User.class))).thenReturn(true);
+
+			var res = fachstelleRegistrationController.register(registration, model, authentication);
+
+			assertThat(res).isEqualTo("success");
+		}
+
+		@Test
+		void shouldNotRegister() {
+			when(registration.key()).thenReturn("otherKey");
+			var res = fachstelleRegistrationController.register(registration, model, authentication);
+
+			assertThat(res).isEqualTo("error");
+		}
+
+		@Test
+		void shouldSetErrorOnRegistrationKeyError() {
+			when(registration.key()).thenReturn("otherKey");
+
+			fachstelleRegistrationController.register(registration, model, authentication);
+
+			verify(model).addAttribute("errorMessageKey", "error_registration_key_does_not_exist");
+		}
+
+		@Test
+		void shouldSetErrorOnRegistrationError() {
+			when(registrationService.register(any(User.class))).thenReturn(false);
+
+			var res = fachstelleRegistrationController.register(registration, model, authentication);
+
+			assertThat(res).isEqualTo("error");
+		}
+
+	}
+
+	@Nested
+	class TestRegistrationTimeout {
+
+		@Mock
+		private Model model;
+
+		@Mock
+		private Authentication authentication;
+
+		@Mock
+		private Registration registration;
+
+		@BeforeEach
+		void setUp() {
+			when(authentication.getName()).thenReturn("user");
+			var user = create();
+			user.setRegistrationKey("key");
+			user.setRegistrationKeyExpiresAt(Instant.now().minus(10, ChronoUnit.MINUTES));
+			when(userDetailService.loadUserByUsername("user")).thenReturn(user);
+			when(registration.key()).thenReturn("key");
+		}
+
+		@Test
+		void shouldSetErrorOnRegistrationExpired() {
+			fachstelleRegistrationController.register(registration, model, authentication);
+
+			verify(model).addAttribute("errorMessageKey", "error_registration_key_expired");
+		}
+
+	}
+
+	@Nested
+	class TestCanRegister {
+
+		User user;
+
+		@BeforeEach
+		void setUp() {
+			user = create();
+			user.setRegistrationKey("key");
+			user.setRegistrationKeyExpiresAt(Instant.now().plus(10, ChronoUnit.MINUTES));
+		}
+
+		@Test
+		void shouldReturnOk() {
+			var res = fachstelleRegistrationController.canRegister(user, "key");
+
+			assertThat(res).isEqualTo("ok");
+		}
+
+		@Test
+		void shouldReturnExpiredErrorKey() {
+			user.setRegistrationKeyExpiresAt(Instant.now().minus(10, ChronoUnit.MINUTES));
+
+			var res = fachstelleRegistrationController.canRegister(user, "key");
+
+			assertThat(res).isEqualTo("error_registration_key_expired");
+		}
+
+		@Test
+		void shouldReturnNotFoundErrorKey() {
+			var res = fachstelleRegistrationController.canRegister(user, "otherHey");
+
+			assertThat(res).isEqualTo("error_registration_key_does_not_exist");
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteServiceTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..496f664384460e6adeab4fe6a1d6213d6910f74c
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRemoteServiceTest.java
@@ -0,0 +1,92 @@
+package de.ozgcloud.fachstelle.registration;
+
+import static de.ozgcloud.fachstelle.registration.FachstelleRegistrationRemoteService.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestClient;
+
+import de.ozgcloud.fachstelle.security.User;
+import de.ozgcloud.fachstelle.security.UserTestFactory;
+
+@ExtendWith(MockitoExtension.class)
+class FachstelleRegistrationRemoteServiceTest {
+
+	@Spy
+	@InjectMocks
+	private FachstelleRegistrationRemoteService service;
+
+	@Mock
+	private RestClient restClient;
+
+	@Mock
+	private FachstelleRegistrationRequestMapper requestMapper;
+
+	@Nested
+	class TestRegister {
+		private static final User user = UserTestFactory.create();
+
+		private RestClient.ResponseSpec responseSpec;
+
+		@BeforeEach
+		void setUp() {
+			var request = GrpcFachstelleRegistrationRequestTestFactory.create();
+			var uriSpec = mock(RestClient.RequestBodyUriSpec.class);
+			var bodySpec = mock(RestClient.RequestBodySpec.class);
+			responseSpec = mock(RestClient.ResponseSpec.class);
+
+			when(requestMapper.toFachstelleRegistrationRequest(user)).thenReturn(request);
+			when(restClient.post()).thenReturn(uriSpec);
+			when(uriSpec.uri(REGISTER_FACHSTELLE_URI)).thenReturn(bodySpec);
+			when(bodySpec.contentType(MediaType.APPLICATION_JSON)).thenReturn(bodySpec);
+			when(bodySpec.body(request)).thenReturn(bodySpec);
+			when(bodySpec.retrieve()).thenReturn(responseSpec);
+		}
+
+		@Test
+		void shouldCallRestClient() {
+			when(responseSpec.toBodilessEntity()).thenReturn(new ResponseEntity<>(HttpStatus.OK));
+
+			service.register(user);
+
+			verify(restClient).post();
+		}
+
+		@Test
+		void shouldCallRequestMapper() {
+			when(responseSpec.toBodilessEntity()).thenReturn(new ResponseEntity<>(HttpStatus.OK));
+
+			service.register(user);
+
+			verify(requestMapper).toFachstelleRegistrationRequest(user);
+		}
+
+		@Test
+		void shouldRegisterSuccessfully() {
+			when(responseSpec.toBodilessEntity()).thenReturn(new ResponseEntity<>(HttpStatus.OK));
+
+			var result = service.register(user);
+			assertTrue(result);
+		}
+
+		@Test
+		void shouldHaveRegistrationError() {
+			when(responseSpec.toBodilessEntity()).thenReturn(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+
+			var result = service.register(user);
+			assertFalse(result);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapperTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb11baf9a7655ed079f676f47b3bb7f80475ab15
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationRequestMapperTest.java
@@ -0,0 +1,24 @@
+package de.ozgcloud.fachstelle.registration;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.fachstelle.security.UserTestFactory;
+
+@ExtendWith(MockitoExtension.class)
+class FachstelleRegistrationRequestMapperTest {
+	@InjectMocks
+	private final FachstelleRegistrationRequestMapper mapper = Mappers.getMapper(FachstelleRegistrationRequestMapper.class);
+
+	@Test
+	void shouldMap() {
+		var request = mapper.toFachstelleRegistrationRequest(UserTestFactory.create());
+
+		assertThat(request).usingRecursiveComparison().isEqualTo(GrpcFachstelleRegistrationRequestTestFactory.create());
+	}
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationServiceTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec79fb58b3c9285163b33b1e44be90bc04b9e96b
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/FachstelleRegistrationServiceTest.java
@@ -0,0 +1,48 @@
+package de.ozgcloud.fachstelle.registration;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import de.ozgcloud.fachstelle.security.User;
+import de.ozgcloud.fachstelle.security.UserTestFactory;
+
+@ExtendWith(MockitoExtension.class)
+class FachstelleRegistrationServiceTest {
+	@InjectMocks
+	private FachstelleRegistrationService service;
+
+	@Mock
+	private FachstelleRegistrationRemoteService remoteService;
+
+	@Nested
+	class TestRegister {
+		private static final User user = UserTestFactory.create();
+
+		@BeforeEach
+		void init() {
+			when(remoteService.register(user)).thenReturn(true);
+		}
+
+		@Test
+		void shouldCallRemoteService() {
+			service.register(user);
+
+			verify(remoteService).register(user);
+		}
+
+		@Test
+		void shouldReturnRegistrationResult() {
+			var result = service.register(user);
+
+			assertTrue(result);
+		}
+	}
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/GrpcFachstelleRegistrationRequestTestFactory.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/GrpcFachstelleRegistrationRequestTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..d8219522d3883707db017c2d63b4f360538aafe9
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/registration/GrpcFachstelleRegistrationRequestTestFactory.java
@@ -0,0 +1,18 @@
+package de.ozgcloud.fachstelle.registration;
+
+import de.ozgcloud.fachstelle.proxy.FachstellenproxyGrpcFachstelleRegistrationRequest;
+import de.ozgcloud.fachstelle.security.UserTestFactory;
+
+public class GrpcFachstelleRegistrationRequestTestFactory {
+	public static FachstellenproxyGrpcFachstelleRegistrationRequest create() {
+		return new FachstellenproxyGrpcFachstelleRegistrationRequest()
+		  .mukId(UserTestFactory.USER_ID)
+		  .firmenName(UserTestFactory.COMPANY_NAME)
+		  .rechtsform(UserTestFactory.LEGAL_FORM)
+		  .rechtsformText(UserTestFactory.LEGAL_FORM_TEXT)
+		  .registerNummer(UserTestFactory.REGISTER_NUMBER)
+		  .registerArt(UserTestFactory.REGISTER_TYPE)
+		  .emailAdresse(UserTestFactory.EMAIL_ADDRESS)
+		  .anschrift(UserTestFactory.ADDRESS);
+	}
+}
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/representation/RepresentationControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/representation/RepresentationControllerITCase.java
index a0fb15ef71c0afe60c48a693c327d05655140e75..0a18ccc0fe062ab081d00607f8d1cbb08c50af11 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/representation/RepresentationControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/representation/RepresentationControllerITCase.java
@@ -47,17 +47,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class RepresentationControllerITCase {
 
@@ -115,9 +117,9 @@ class RepresentationControllerITCase {
 	@SneakyThrows
 	private ResultActions performRequest() {
 		return mockMvc.perform(
-		  get(RepresentationController.PATH + "?eingangId=" + EingangTestFactory.ID)
-			.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-			.with(csrf().asHeader()));
+				get(RepresentationController.PATH + "?eingangId=" + EingangTestFactory.ID)
+						.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+						.with(csrf().asHeader()));
 	}
 
 }
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/resource/OzgcloudResourceControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/resource/OzgcloudResourceControllerITCase.java
index 1988cc22cac0d477f9558467e58f78ce6bf4e7f4..2617195b04ea167311c410e6c414eae32e6ebf5d 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/resource/OzgcloudResourceControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/resource/OzgcloudResourceControllerITCase.java
@@ -51,87 +51,89 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+        "ozgcloud.fachstelle.logout-success-url=http://logout",
+        "ozgcloud.fachstelle.login-redirect-url=http://login",
+        "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+        "ozgcloud.fachstellen-proxy.base-url=http://proxy",
+        "keycloak.auth-server-url=http://keycloak",
+        "keycloak.realm=fachstelle",
+        "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+        "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+        "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+        "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+        "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+        "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+        "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class OzgcloudResourceControllerITCase {
 
-	@Autowired
-	private MockMvc mockMvc;
+    @Autowired
+    private MockMvc mockMvc;
 
-	@Nested
-	class TestGetOzgcloudResource {
+    @Nested
+    class TestGetOzgcloudResource {
 
-		private static final String COLLABORATION_MANAGER_ADDRESS = VorgangWithCollaborationManagerAddressTestFactory.COLLABORATION_MANAGER_ADDRESS;
-		private static final String VORGANG_ID = VorgangTestFactory.ID;
-		private static final String VALID_URI = COLLABORATION_MANAGER_ADDRESS + "/vorgangs/" + VORGANG_ID;
-		private static final String INVALID_URI = COLLABORATION_MANAGER_ADDRESS + ":123/" + VORGANG_ID;
+        private static final String COLLABORATION_MANAGER_ADDRESS = VorgangWithCollaborationManagerAddressTestFactory.COLLABORATION_MANAGER_ADDRESS;
+        private static final String VORGANG_ID = VorgangTestFactory.ID;
+        private static final String VALID_URI = COLLABORATION_MANAGER_ADDRESS + "/vorgangs/" + VORGANG_ID;
+        private static final String INVALID_URI = COLLABORATION_MANAGER_ADDRESS + ":123/" + VORGANG_ID;
 
-		@Test
-		void shouldReturnStatusOk() throws Exception {
-			var response = doRequest(VALID_URI);
+        @Test
+        void shouldReturnStatusOk() throws Exception {
+            var response = doRequest(VALID_URI);
 
-			response.andExpect(status().isOk());
-		}
+            response.andExpect(status().isOk());
+        }
 
-		@Test
-		void shouldHaveVorgangLink() throws Exception {
-			var response = doRequest(VALID_URI);
+        @Test
+        void shouldHaveVorgangLink() throws Exception {
+            var response = doRequest(VALID_URI);
 
-			response.andExpect(jsonPath("$._links.vorgang.href").value(StringEndsWith.endsWith(
-			  "/api/vorgangs/" + VORGANG_ID + "?" + PARAM_COLLABORATION_MANAGER_ADDRESS + "=" + COLLABORATION_MANAGER_ADDRESS)));
-		}
+            response.andExpect(jsonPath("$._links.vorgang.href").value(StringEndsWith.endsWith(
+                    "/api/vorgangs/" + VORGANG_ID + "?" + PARAM_COLLABORATION_MANAGER_ADDRESS + "=" + COLLABORATION_MANAGER_ADDRESS)));
+        }
 
-		@Test
-		void shouldHaveSelfLink() throws Exception {
-			var encodedUri = URLEncoder.encode(VALID_URI, StandardCharsets.UTF_8);
+        @Test
+        void shouldHaveSelfLink() throws Exception {
+            var encodedUri = URLEncoder.encode(VALID_URI, StandardCharsets.UTF_8);
 
-			var response = doRequest(VALID_URI);
+            var response = doRequest(VALID_URI);
 
-			response.andExpect(
-			  jsonPath("$._links.self.href").value(StringEndsWith.endsWith(OzgcloudResourceController.PATH + "?" + PARAM_URI + "=" + encodedUri)));
-		}
+            response.andExpect(
+                    jsonPath("$._links.self.href").value(StringEndsWith.endsWith(OzgcloudResourceController.PATH + "?" + PARAM_URI + "=" + encodedUri)));
+        }
 
-		@Test
-		void shouldReturnStatusNotFound() throws Exception {
-			var response = doRequest(INVALID_URI);
+        @Test
+        void shouldReturnStatusNotFound() throws Exception {
+            var response = doRequest(INVALID_URI);
 
-			response.andExpect(status().isNotFound());
-		}
+            response.andExpect(status().isNotFound());
+        }
 
-		@Test
-		void shouldReturnBadRequestOnNoRequestParam() throws Exception {
-			var response = doRequestWithQueryString("");
+        @Test
+        void shouldReturnBadRequestOnNoRequestParam() throws Exception {
+            var response = doRequestWithQueryString("");
 
-			response.andExpect(status().isBadRequest());
-		}
+            response.andExpect(status().isBadRequest());
+        }
 
-		@Test
-		void shouldReturnBadRequestOnEmptyUri() throws Exception {
-			var response = doRequestWithQueryString("?" + PARAM_URI + "=");
+        @Test
+        void shouldReturnBadRequestOnEmptyUri() throws Exception {
+            var response = doRequestWithQueryString("?" + PARAM_URI + "=");
 
-			response.andExpect(status().isBadRequest());
-		}
+            response.andExpect(status().isBadRequest());
+        }
 
-		@SneakyThrows
-		private ResultActions doRequest(String uri) {
-			return doRequestWithQueryString("?" + PARAM_URI + "=" + uri);
-		}
+        @SneakyThrows
+        private ResultActions doRequest(String uri) {
+            return doRequestWithQueryString("?" + PARAM_URI + "=" + uri);
+        }
 
-		@SneakyThrows
-		private ResultActions doRequestWithQueryString(String queryString) {
-			return mockMvc.perform(get(OzgcloudResourceController.PATH + queryString));
-		}
+        @SneakyThrows
+        private ResultActions doRequestWithQueryString(String queryString) {
+            return mockMvc.perform(get(OzgcloudResourceController.PATH + queryString));
+        }
 
-	}
+    }
 
 }
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/CurrentUserServiceTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/CurrentUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f88895389a11d10a5de9fad62a84b87dfa24fa22
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/CurrentUserServiceTest.java
@@ -0,0 +1,94 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+
+import de.ozgcloud.fachstelle.common.errorhandling.SamlTokenNotFoundException;
+
+@ExtendWith(MockitoExtension.class)
+class CurrentUserServiceTest {
+
+	@Spy
+	@InjectMocks
+	private CurrentUserService service;
+
+	@Mock
+	private AuthenticationTrustResolver trustResolver;
+
+	@Nested
+	class TestGetAuthentication {
+
+		@Test
+		void shouldReturnAuthentication() {
+			try (var securityContextHolder = mockStatic(SecurityContextHolder.class)) {
+				var authentication = mock(Saml2Authentication.class);
+				var context = mock(SecurityContext.class);
+				when(context.getAuthentication()).thenReturn(authentication);
+				securityContextHolder.when(SecurityContextHolder::getContext).thenReturn(context);
+
+				assertThat(service.getAuthentication()).isEqualTo(authentication);
+			}
+		}
+
+		@Test
+		void shouldThrowExceptionOnUntrustedAuthentication() {
+			try (var securityContextHolder = mockStatic(SecurityContextHolder.class)) {
+				var authentication = mock(AnonymousAuthenticationToken.class);
+				var context = mock(SecurityContext.class);
+				when(trustResolver.isAnonymous(authentication)).thenReturn(true);
+				when(context.getAuthentication()).thenReturn(authentication);
+				securityContextHolder.when(SecurityContextHolder::getContext).thenReturn(context);
+
+				assertThatExceptionOfType(IllegalStateException.class).isThrownBy(service::getAuthentication);
+			}
+		}
+
+		@Test
+		void shouldThrowExceptionOnNullAuthentication() {
+			try (var securityContextHolder = mockStatic(SecurityContextHolder.class)) {
+				var context = mock(SecurityContext.class);
+				when(context.getAuthentication()).thenReturn(null);
+				securityContextHolder.when(SecurityContextHolder::getContext).thenReturn(context);
+
+				assertThatExceptionOfType(IllegalStateException.class).isThrownBy(service::getAuthentication);
+			}
+		}
+
+	}
+
+	@Nested
+	class TestGetSamlToken {
+
+		@Test
+		void shouldReturnSamlToken() {
+			var authentication = mock(Saml2Authentication.class);
+			when(authentication.getSaml2Response()).thenReturn(UserTestFactory.SAML_TOKEN);
+			doReturn(authentication).when(service).getAuthentication();
+
+			assertThat(service.getSamlToken()).isEqualTo(UserTestFactory.SAML_TOKEN);
+		}
+
+		@Test
+		void shouldThrowExceptionOnNonSamlAuthentication() {
+			var authentication = mock(AnonymousAuthenticationToken.class);
+			doReturn(authentication).when(service).getAuthentication();
+
+			assertThatExceptionOfType(SamlTokenNotFoundException.class).isThrownBy(service::getSamlToken);
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandlerTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..57b557920691f55ecf0b8ca043e8ea7a9b504f2f
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/FachstelleLogoutSuccessHandlerTest.java
@@ -0,0 +1,45 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+
+@ExtendWith(MockitoExtension.class)
+class FachstelleLogoutSuccessHandlerTest {
+
+	@Spy
+	@InjectMocks
+	private FachstelleLogoutSuccessHandler fachstelleLogoutSuccessHandler;
+	@Mock
+	private InMemoryUserDetailService userDetailService;
+
+	@Test
+	void shouldCallLogout() throws ServletException, IOException {
+		Authentication authentication = mock(Authentication.class);
+		when(authentication.getPrincipal()).thenReturn(UserTestFactory.create());
+
+		fachstelleLogoutSuccessHandler.onLogoutSuccess(mock(HttpServletRequest.class), mock(HttpServletResponse.class), authentication);
+
+		verify(userDetailService).logout(any(User.class));
+	}
+
+	@Test
+	void shouldNotCallLogout() throws ServletException, IOException {
+		fachstelleLogoutSuccessHandler.onLogoutSuccess(mock(HttpServletRequest.class), mock(HttpServletResponse.class), mock(Authentication.class));
+
+		verify(userDetailService, never()).logout(any(User.class));
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailServiceTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ef543bcdf5e04cfb21e0d2dbf88cfbf598c54a5
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/InMemoryUserDetailServiceTest.java
@@ -0,0 +1,115 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import com.google.common.testing.FakeTicker;
+
+@ExtendWith(MockitoExtension.class)
+class InMemoryUserDetailServiceTest {
+
+	private final InMemoryUserDetailService userDetailService = new InMemoryUserDetailService();
+	private User user;
+
+	@Nested
+	class TestAddingUser {
+
+		@BeforeEach
+		void setup() {
+			user = UserTestFactory.create();
+			userDetailService.addUser(user);
+		}
+
+		@Test
+		void shouldAddUser() {
+			assertThat(userDetailService.getUser(UserTestFactory.USER_ID)).isNotNull();
+		}
+
+		@Test
+		void shouldLoadUser() {
+			userDetailService.addUser(user);
+
+			assertThat(userDetailService.loadUserByUsername(UserTestFactory.USER_NAME)).isNotNull();
+		}
+
+	}
+
+	@Nested
+	class TestSetUser {
+
+		@BeforeEach
+		void setup() {
+			user = UserTestFactory.create();
+			userDetailService.setUser(user);
+		}
+
+		@Test
+		void shouldSetUser() {
+			assertThat(userDetailService.getUser(UserTestFactory.USER_ID)).isNotNull();
+		}
+
+	}
+
+	@Nested
+	class TestExpiringCodeCache {
+
+		FakeTicker ticker = new FakeTicker();
+
+		@BeforeEach
+		void setup() {
+			user = UserTestFactory.create();
+
+			userDetailService.addUser(user);
+
+			ticker.advance(2, TimeUnit.SECONDS);
+		}
+
+	}
+
+	@Nested
+	class TestUserMapCleanUp {
+
+		private User expiredUser;
+		private User validUser;
+
+		@BeforeEach
+		void setup() {
+			validUser = UserTestFactory.create();
+			userDetailService.setUser(validUser);
+			expiredUser = UserTestFactory.createBuilder()
+			  .id(UUID.randomUUID().toString())
+			  .build();
+		}
+
+		@Test
+		void shouldRemoveExpiredUser() {
+			userDetailService.userCleanUp();
+
+			assertThat(userDetailService.getUser(expiredUser.getId())).isNull();
+		}
+
+		@Test
+		void shouldNotRemoveValidUser() {
+			userDetailService.userCleanUp();
+
+			assertThat(userDetailService.getUser(validUser.getId())).isNotNull();
+		}
+
+		@Test
+		void shouldRemoveOnLogout() {
+			userDetailService.logout(validUser);
+
+			assertThat(userDetailService.getUser(validUser.getId())).isNull();
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithmTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithmTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b37b9e4c86befd46819d69f43be1cae673849394
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SHA256withRSAAndMGF1SignatureAlgorithmTest.java
@@ -0,0 +1,46 @@
+package de.ozgcloud.fachstelle.security;
+
+import static de.ozgcloud.fachstelle.security.SHA256withRSAAndMGF1SignatureAlgorithm.*;
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.opensaml.xmlsec.algorithm.AlgorithmDescriptor;
+
+class SHA256withRSAAndMGF1SignatureAlgorithmTest {
+
+	@Test
+	void shouldGetKey() {
+		var algorithm = new SHA256withRSAAndMGF1SignatureAlgorithm();
+
+		assertThat(algorithm.getKey()).isEqualTo(RSA_ALGORITHM_ID);
+	}
+
+	@Test
+	void shouldGetURI() {
+		var algorithm = new SHA256withRSAAndMGF1SignatureAlgorithm();
+
+		assertThat(algorithm.getURI()).isEqualTo(RSA_SHA256_MGF1_ALGORITHM_URL);
+	}
+
+	@Test
+	void shouldGetType() {
+		var algorithm = new SHA256withRSAAndMGF1SignatureAlgorithm();
+
+		assertThat(algorithm.getType()).isEqualTo(AlgorithmDescriptor.AlgorithmType.Signature);
+	}
+
+	@Test
+	void shouldGetJCAAlgorithmID() {
+		var algorithm = new SHA256withRSAAndMGF1SignatureAlgorithm();
+
+		assertThat(algorithm.getJCAAlgorithmID()).isEqualTo(RSA_SHA256_MGF1_ALGORITHM_ID);
+	}
+
+	@Test
+	void shouldGetDigest() {
+		var algorithm = new SHA256withRSAAndMGF1SignatureAlgorithm();
+
+		assertThat(algorithm.getDigest()).isEqualTo(SHA256_ALGORITHM_ID);
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2DecrypterTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2DecrypterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cccb4fcc9f01f2bec0111e17177267cee3fd6043
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2DecrypterTest.java
@@ -0,0 +1,130 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.config.InitializationException;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.core.xml.io.UnmarshallerFactory;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.EncryptedAssertion;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
+import org.opensaml.saml.saml2.encryption.Decrypter;
+import org.opensaml.xmlsec.encryption.support.DecryptionException;
+import org.springframework.core.io.ClassPathResource;
+
+@ExtendWith(MockitoExtension.class)
+class Saml2DecrypterTest {
+
+	private Saml2Decrypter saml2Decrypter;
+	private String samlResponse;
+
+	@BeforeEach
+	void init() throws NoSuchFieldException, IllegalAccessException, InitializationException {
+		saml2Decrypter = new Saml2Decrypter();
+
+		var privateKeyLocationField = Saml2Decrypter.class.getDeclaredField("decryptionPrivateKeyLocation");
+		privateKeyLocationField.setAccessible(true);
+		privateKeyLocationField.set(saml2Decrypter, new ClassPathResource("mujina-test.key"));
+
+		var certificateLocationField = Saml2Decrypter.class.getDeclaredField("decryptionCertificateLocation");
+		certificateLocationField.setAccessible(true);
+		certificateLocationField.set(saml2Decrypter, new ClassPathResource("mujina-test.crt"));
+
+		saml2Decrypter.init();
+	}
+
+	@Nested
+	class TestInit {
+
+		@Test
+		void shouldHaveDecrypter() {
+			assertThat(saml2Decrypter.getDecrypter()).isNotNull();
+		}
+
+	}
+
+	@Nested
+	class TestGetDecryptedAttribute {
+
+		private static final String ATTRIBUTE_NAME = "TestAttributeName";
+
+		@Mock
+		private UnmarshallerFactory unmarshallerFactory;
+
+		@Mock
+		private ResponseUnmarshaller responseUnmarshaller;
+
+		@Mock
+		private XMLObjectProviderRegistry providerRegistry;
+
+		@Mock
+		private Response response;
+
+		@Mock
+		private EncryptedAssertion encryptedAssertion;
+
+		@Mock
+		private Decrypter decrypter;
+
+		@Mock
+		private AttributeStatement attributeStatement;
+
+		@Mock
+		private Attribute attribute;
+
+		@Mock
+		private Assertion assertion;
+
+		@BeforeEach
+		void init() throws IOException, UnmarshallingException, DecryptionException {
+			samlResponse = FileUtils.readFileToString(new File("src/test/resources/SamlResponse.xml"), Charset.defaultCharset());
+
+			when(decrypter.decrypt(encryptedAssertion)).thenReturn(assertion);
+			when(attribute.getName()).thenReturn(ATTRIBUTE_NAME);
+			when(assertion.getStatements()).thenReturn(List.of(attributeStatement, attributeStatement));
+			when(attributeStatement.getAttributes()).thenReturn(Collections.singletonList(attribute));
+			when(response.getEncryptedAssertions()).thenReturn(Collections.singletonList(encryptedAssertion));
+			when(response.getAssertions()).thenReturn(new ArrayList<>(Collections.singletonList(assertion)));
+
+			when(responseUnmarshaller.unmarshall(any())).thenReturn(response);
+			when(unmarshallerFactory.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME)).thenReturn(responseUnmarshaller);
+			when(providerRegistry.getUnmarshallerFactory()).thenReturn(unmarshallerFactory);
+
+			saml2Decrypter.setDecrypter(decrypter);
+		}
+
+		@Test
+		void shouldDecryptAttribute() {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(XMLObjectProviderRegistry.class)).thenReturn(providerRegistry);
+
+				var attribute = saml2Decrypter.getDecryptedAttribute(samlResponse, ATTRIBUTE_NAME);
+
+				assertThat(attribute).isNotNull();
+			}
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2ParserTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2ParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..18d5efb9e7a24586309eda9a196991855095a264
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/Saml2ParserTest.java
@@ -0,0 +1,115 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.core.xml.io.UnmarshallerFactory;
+import org.opensaml.core.xml.io.UnmarshallingException;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller;
+import org.w3c.dom.Element;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+@ExtendWith(MockitoExtension.class)
+class Saml2ParserTest {
+
+	@Nested
+	class TestGetParserPool {
+
+		@Test
+		void shouldHaveParserPool() throws ComponentInitializationException {
+			assertThat(Saml2Parser.getParserPool()).isNotNull();
+		}
+
+	}
+
+	@Nested
+	class TestGetResponseUnmarshaller {
+
+		@Mock
+		private UnmarshallerFactory unmarshallerFactory;
+
+		@Mock
+		private ResponseUnmarshaller responseUnmarshaller;
+
+		@Mock
+		private XMLObjectProviderRegistry providerRegistry;
+
+		@Test
+		void shouldHaveResponseUnmarshaller() {
+			when(unmarshallerFactory.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME)).thenReturn(responseUnmarshaller);
+			when(providerRegistry.getUnmarshallerFactory()).thenReturn(unmarshallerFactory);
+
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(XMLObjectProviderRegistry.class)).thenReturn(providerRegistry);
+
+				assertThat(Saml2Parser.getResponseUnmarshaller()).isNotNull();
+			}
+		}
+
+	}
+
+	@Nested
+	class TestParse {
+
+		@Mock
+		private UnmarshallerFactory unmarshallerFactory;
+
+		@Mock
+		private ResponseUnmarshaller responseUnmarshaller;
+
+		@Mock
+		private XMLObjectProviderRegistry providerRegistry;
+
+		private String samlResponse;
+
+		@BeforeEach
+		void init() throws IOException, UnmarshallingException {
+			samlResponse = FileUtils.readFileToString(new File("src/test/resources/SamlResponse.xml"), Charset.defaultCharset());
+
+			when(responseUnmarshaller.unmarshall(any())).thenReturn(mock(Response.class));
+			when(unmarshallerFactory.getUnmarshaller(Response.DEFAULT_ELEMENT_NAME)).thenReturn(responseUnmarshaller);
+			when(providerRegistry.getUnmarshallerFactory()).thenReturn(unmarshallerFactory);
+		}
+
+		@Test
+		void shouldParseSamlToken() {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(XMLObjectProviderRegistry.class)).thenReturn(providerRegistry);
+
+				assertThat(Saml2Parser.parse(samlResponse)).isNotNull();
+			}
+		}
+
+		@Test
+		void shouldCreateXmlDocument() throws UnmarshallingException {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(XMLObjectProviderRegistry.class)).thenReturn(providerRegistry);
+
+				var xmlElementArgumentCaptor = ArgumentCaptor.forClass(Element.class);
+
+				Saml2Parser.parse(samlResponse);
+
+				verify(responseUnmarshaller).unmarshall(xmlElementArgumentCaptor.capture());
+				assertThat(xmlElementArgumentCaptor.getValue().getTagName()).isEqualTo("saml2p:Response");
+			}
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandlerTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6993ad343c02d2c744ec9efe191888fb39e9c85a
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityExceptionHandlerTest.java
@@ -0,0 +1,72 @@
+package de.ozgcloud.fachstelle.security;
+
+import static de.ozgcloud.fachstelle.security.SecurityExceptionHandler.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class SecurityExceptionHandlerTest {
+
+	SecurityExceptionHandler exceptionHandler = new SecurityExceptionHandler();
+	@Mock
+	HttpServletRequest request;
+	@Mock
+	SecurityException exception;
+	@Mock
+	HttpSession session;
+
+	@BeforeEach
+	void setUp() {
+		when(request.getSession()).thenReturn(session);
+	}
+
+	@Test
+	void shouldCreateNotFoundView() {
+		var res = exceptionHandler.handleSecurityException(request, exception);
+
+		assertThat(res).isNotNull();
+	}
+
+	@Test
+	void shouldHaveMessage() {
+		when(exception.getMessage()).thenReturn("message");
+
+		var res = exceptionHandler.handleSecurityException(request, exception);
+
+		assertThat(res.getModel()).containsEntry("exception", "message");
+	}
+
+	@Test
+	void shouldHaveUrl() {
+		var val = new StringBuffer("http://localhost:8080/");
+		when(request.getRequestURL()).thenReturn(val);
+
+		var res = exceptionHandler.handleSecurityException(request, exception);
+
+		assertThat(res.getModel()).containsEntry("url", val);
+	}
+
+	@Test
+	void shouldHaveViewName() {
+		var res = exceptionHandler.handleSecurityException(request, exception);
+
+		assertThat(res.getViewName()).isEqualTo(DEFAULT_ERROR_VIEW);
+	}
+
+	@Test
+	void shouldInvalidateSession() {
+		exceptionHandler.handleSecurityException(request, exception);
+
+		verify(session).invalidate();
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityProviderTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d8a6df9de3d9e57ff16a3641e970982441088b2
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/SecurityProviderTest.java
@@ -0,0 +1,76 @@
+package de.ozgcloud.fachstelle.security;
+
+import static de.ozgcloud.fachstelle.security.SHA256withRSAAndMGF1SignatureAlgorithm.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.xmlsec.algorithm.AlgorithmRegistry;
+
+public class SecurityProviderTest {
+
+	private static final String BOUNCY_CASTLE_PROVIDER_ID = "BC";
+
+	private final SecurityProvider securityProvider = new SecurityProvider();
+
+	@Nested
+	class TestAfterPropertiesSet {
+
+		@BeforeEach
+		void init() {
+			Security.removeProvider(BOUNCY_CASTLE_PROVIDER_ID);
+		}
+
+		@Test
+		void shouldNotAddBouncyCastleProvider() {
+			assertThat(Security.getProvider(BOUNCY_CASTLE_PROVIDER_ID)).isNull();
+		}
+
+		@Test
+		void shouldAddBouncyCastleProvider() {
+			securityProvider.afterPropertiesSet();
+
+			assertThat(Security.getProvider(BOUNCY_CASTLE_PROVIDER_ID)).isInstanceOf(BouncyCastleProvider.class);
+		}
+
+		@Test
+		void shouldHandleAlgorithmRegistryIsNull() {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(AlgorithmRegistry.class)).thenReturn(null);
+
+				securityProvider.afterPropertiesSet();
+
+				assertThat(ConfigurationService.get(AlgorithmRegistry.class)).isNull();
+			}
+		}
+
+		@Test
+		void shouldNotRegisterSignatureAlgorithms() {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(AlgorithmRegistry.class)).thenReturn(new AlgorithmRegistry());
+
+				assertThat(ConfigurationService.get(AlgorithmRegistry.class).get(RSA_SHA256_MGF1_ALGORITHM_URL)).isNull();
+			}
+		}
+
+		@Test
+		void shouldRegisterSignatureAlgorithms() {
+			try (var configService = mockStatic(ConfigurationService.class)) {
+				configService.when(() -> ConfigurationService.get(AlgorithmRegistry.class)).thenReturn(new AlgorithmRegistry());
+
+				securityProvider.afterPropertiesSet();
+
+				assertThat(ConfigurationService.get(AlgorithmRegistry.class).get(RSA_SHA256_MGF1_ALGORITHM_URL)).isInstanceOf(
+				  SHA256withRSAAndMGF1SignatureAlgorithm.class);
+			}
+		}
+
+	}
+
+}
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandlerTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0179b2bae4501b002aa39063d47cf385ab4078f
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UrlAuthenticationSuccessHandlerTest.java
@@ -0,0 +1,174 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+
+@ExtendWith(MockitoExtension.class)
+class UrlAuthenticationSuccessHandlerTest {
+
+	private static final String HTTP_TEST = "http://test";
+
+	private UrlAuthenticationSuccessHandler successHandler;
+
+	@Mock
+	private InMemoryUserDetailService userService;
+
+	@Mock
+	private UserMapper userMapper;
+
+	@Nested
+	class TestOnSuccess {
+
+		UrlAuthenticationSuccessHandler handler;
+		@Mock
+		private HttpServletRequest request;
+		@Mock
+		private HttpServletResponse response;
+		@Mock
+		private Saml2Authentication authentication;
+
+		@BeforeEach
+		void setup() {
+			handler = spy(new UrlAuthenticationSuccessHandler(userService, userMapper));
+			Collection<? extends GrantedAuthority> grantedAuthorities = List.of(new DefaultRole());
+			doReturn(grantedAuthorities).when(authentication).getAuthorities();
+		}
+
+		@Test
+		void shouldCallHandle() throws IOException {
+			handler.onAuthenticationSuccess(request, response, authentication);
+
+			verify(handler).handle(any(), any(), any());
+		}
+
+		@Test
+		void shouldCallClearAuthenticationAttributes() throws IOException {
+			handler.onAuthenticationSuccess(request, response, authentication);
+
+			verify(handler).clearAuthenticationAttributes(any());
+		}
+
+	}
+
+	@Nested
+	class TestWithAuthentication {
+
+		@Mock
+		private Authentication authentication;
+
+		@Test
+		void shouldDetermineTargetUrlForRole() {
+			Collection<? extends GrantedAuthority> grantedAuthorities = List.of(new DefaultRole());
+			doReturn(grantedAuthorities).when(authentication).getAuthorities();
+
+			successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+
+			var url = successHandler.determineTargetUrl(authentication);
+
+			assertThat(url).isEqualTo("/preregister");
+		}
+
+		@Test
+		void shouldDetermineTargetUrlMissingRole() {
+			Collection<? extends GrantedAuthority> grantedAuthorities = List.of();
+			doReturn(grantedAuthorities).when(authentication).getAuthorities();
+
+			successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+
+			assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> successHandler.determineTargetUrl(authentication))
+			  .withMessage("Invalid role! User is missing role ROLE_USER");
+		}
+
+	}
+
+	@Nested
+	class TestClearLoginSession {
+
+		@Mock
+		private HttpServletRequest request;
+
+		@Mock
+		private HttpSession session;
+
+		@Test
+		void shouldClearSession() {
+			when(request.getSession(anyBoolean())).thenReturn(session);
+
+			successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+
+			successHandler.clearAuthenticationAttributes(request);
+
+			verify(session).removeAttribute(any());
+		}
+
+		@Test
+		void shouldHandleNullSession() {
+			assertThatNoException().isThrownBy(() -> {
+				successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+				successHandler.clearAuthenticationAttributes(request);
+			});
+
+		}
+
+	}
+
+	@Nested
+	class TestHandle {
+
+		@Mock
+		private HttpServletRequest request;
+		@Mock
+		private HttpServletResponse response;
+		@Mock
+		private Authentication authentication;
+
+		@BeforeEach
+		void setup() {
+			Collection<? extends GrantedAuthority> grantedAuthorities = List.of(new DefaultRole());
+			doReturn(grantedAuthorities).when(authentication).getAuthorities();
+		}
+
+		@Test
+		void shouldHandle() throws IOException {
+			when(request.getContextPath()).thenReturn(HTTP_TEST);
+			when(response.isCommitted()).thenReturn(Boolean.FALSE);
+			when(response.encodeRedirectURL(anyString())).thenReturn(HTTP_TEST + "/preregister");
+
+			successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+			successHandler.handle(request, response, authentication);
+
+			verify(response).encodeRedirectURL(HTTP_TEST + "/preregister");
+			verify(response).sendRedirect(startsWith(HTTP_TEST + "/preregister"));
+		}
+
+		@Test
+		void shouldHandleCommitted() throws IOException {
+			when(response.isCommitted()).thenReturn(Boolean.TRUE);
+			successHandler = new UrlAuthenticationSuccessHandler(userService, userMapper);
+			successHandler.handle(request, response, authentication);
+
+			verify(response, never()).sendRedirect(anyString());
+		}
+
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserAttributeProviderTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserAttributeProviderTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..13ad3360b608ee0b9f6a207687fc25f5ead4dfe7
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserAttributeProviderTest.java
@@ -0,0 +1,145 @@
+package de.ozgcloud.fachstelle.security;
+
+import static de.ozgcloud.fachstelle.security.UserAttributeProvider.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opensaml.core.xml.XMLObject;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
+import org.w3c.dom.Element;
+
+@ExtendWith(MockitoExtension.class)
+class UserAttributeProviderTest {
+
+	private static final String UNKNOWN_ATTRIBUTE_KEY = "UnknownAttributeKey";
+
+	@Spy
+	@InjectMocks
+	private UserAttributeProvider provider;
+
+	@Mock
+	private Saml2Decrypter saml2Decrypter;
+
+	private DefaultSaml2AuthenticatedPrincipal principal;
+
+	@BeforeEach
+	void setup() {
+		Map<String, List<Object>> attributes = new HashMap<>();
+		attributes.put(UserAttributeProvider.MUK_FIRMENNAME_KEY, List.of(UserTestFactory.COMPANY_NAME));
+		attributes.put(UserAttributeProvider.MUK_RECHTSFORM_KEY, List.of(UserTestFactory.LEGAL_FORM));
+		attributes.put(UserAttributeProvider.MUK_RECHTSFORM_TEXT_KEY, List.of(UserTestFactory.LEGAL_FORM_TEXT));
+		attributes.put(UserAttributeProvider.MUK_REGISTERNUMMER_KEY, List.of(UserTestFactory.REGISTER_NUMBER));
+		attributes.put(UserAttributeProvider.MUK_REGISTERART_KEY, List.of(UserTestFactory.REGISTER_TYPE));
+		attributes.put(UserAttributeProvider.MUK_EMAIL_ADRESSE_KEY, List.of(UserTestFactory.EMAIL_ADDRESS));
+		attributes.put(UserAttributeProvider.MUK_ADRESSE_KEY, List.of(UserTestFactory.ADDRESS));
+		attributes.put(UserAttributeProvider.MUK_VERTRAUENSNIVEAU_KEY, List.of(UserTestFactory.TRUST_LEVEL));
+		attributes.put(UNKNOWN_ATTRIBUTE_KEY, List.of(UserTestFactory.USER_ID));
+
+		principal = new DefaultSaml2AuthenticatedPrincipal(UserTestFactory.USER_ID, attributes);
+	}
+
+	@Test
+	void shouldGetCompanyName() {
+		assertThat(provider.getCompanyName(principal)).isEqualTo(UserTestFactory.COMPANY_NAME);
+	}
+
+	@Test
+	void shouldGetLegalForm() {
+		assertThat(provider.getLegalForm(principal)).isEqualTo(UserTestFactory.LEGAL_FORM);
+	}
+
+	@Test
+	void shouldGetLegalFormText() {
+		assertThat(provider.getLegalFormText(principal)).isEqualTo(UserTestFactory.LEGAL_FORM_TEXT);
+	}
+
+	@Test
+	void shouldGetRegisterNumber() {
+		assertThat(provider.getRegisterNumber(principal)).isEqualTo(UserTestFactory.REGISTER_NUMBER);
+	}
+
+	@Test
+	void shouldGetRegisterType() {
+		assertThat(provider.getRegisterType(principal)).isEqualTo(UserTestFactory.REGISTER_TYPE);
+	}
+
+	@Test
+	void shouldGetEmailAddress() {
+		assertThat(provider.getEmailAddress(principal)).isEqualTo(UserTestFactory.EMAIL_ADDRESS);
+	}
+
+	@Test
+	void shouldGetAddress() {
+		var addressNode = mock(Attribute.class);
+		var strasseNode = createMockXmlObject(SAML_XML_STRASSE_NODE_NAME, UserTestFactory.STREET);
+		var hausnummerNode = createMockXmlObject(SAML_XML_HAUSNUMMER_NODE_NAME, UserTestFactory.HOUSE_NUMBER);
+		var plzNode = createMockXmlObject(SAML_XML_PLZ_NODE_NAME, UserTestFactory.POSTAL_CODE);
+		var ortNode = createMockXmlObject(SAML_XML_ORT_NODE_NAME, UserTestFactory.CITY);
+		var landNode = createMockXmlObject(SAML_XML_LAND_NODE_NAME, UserTestFactory.COUNTRY);
+		var attributeValue = mock(XMLObject.class);
+
+		when(attributeValue.getOrderedChildren()).thenReturn(List.of(strasseNode, hausnummerNode, plzNode, ortNode, landNode));
+		when(addressNode.getAttributeValues()).thenReturn(List.of(attributeValue));
+		when(saml2Decrypter.getDecryptedAttribute(anyString(), anyString())).thenReturn(addressNode);
+
+		assertThat(provider.getAddress("")).isEqualTo(UserTestFactory.ADDRESS);
+	}
+
+	@Test
+	void shouldHaveNullAddress() {
+		var addressNode = mock(Attribute.class);
+		var attributeValue = mock(XMLObject.class);
+
+		when(saml2Decrypter.getDecryptedAttribute(anyString(), anyString())).thenReturn(addressNode);
+		when(attributeValue.getOrderedChildren()).thenReturn(Collections.emptyList());
+		when(addressNode.getAttributeValues()).thenReturn(List.of(attributeValue));
+
+		assertThat(provider.getAddress("")).isNull();
+	}
+
+	@Test
+	void shouldHaveNullAddressDueToException() {
+		when(saml2Decrypter.getDecryptedAttribute(anyString(), anyString())).thenThrow(new Saml2Exception("Decryption error"));
+
+		assertThat(provider.getAddress("")).isNull();
+	}
+
+	@Test
+	void shouldTrustLevel() {
+		assertThat(provider.getTrustLevel(principal)).isEqualTo(UserTestFactory.TRUST_LEVEL);
+	}
+
+	@Test
+	void shouldGetUnknownAttributes() {
+		assertThat(provider.getUnknownAttributes(principal)).hasSize(1);
+	}
+
+	private XMLObject createMockXmlObject(String nodeName, String textContent) {
+		var node = mock(XMLObject.class);
+		var element = mock(Element.class);
+		var elementQName = mock(QName.class);
+		when(elementQName.getLocalPart()).thenReturn(nodeName);
+		when(node.getElementQName()).thenReturn(elementQName);
+		when(element.getTextContent()).thenReturn(textContent);
+		when(node.getDOM()).thenReturn(element);
+
+		return node;
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserMapperTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserMapperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a276c8267e3e08f57d71fa83b6a20de39a1369d1
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserMapperTest.java
@@ -0,0 +1,125 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+
+@ExtendWith(MockitoExtension.class)
+class UserMapperTest {
+
+	@Spy
+	@InjectMocks
+	private UserMapper userMapper;
+
+	@Mock
+	private UserAttributeProvider userAttributeProvider;
+
+	private Saml2Authentication auth;
+
+	@BeforeEach
+	void setup() {
+		auth = mock(Saml2Authentication.class);
+		var principal = new DefaultSaml2AuthenticatedPrincipal(UserTestFactory.USER_ID, Collections.emptyMap());
+		when(auth.getPrincipal()).thenReturn(principal);
+		when(auth.getSaml2Response()).thenReturn("");
+
+		when(userAttributeProvider.getCompanyName(principal)).thenReturn(UserTestFactory.COMPANY_NAME);
+		when(userAttributeProvider.getLegalForm(principal)).thenReturn(UserTestFactory.LEGAL_FORM);
+		when(userAttributeProvider.getLegalFormText(principal)).thenReturn(UserTestFactory.LEGAL_FORM_TEXT);
+		when(userAttributeProvider.getRegisterNumber(principal)).thenReturn(UserTestFactory.REGISTER_NUMBER);
+		when(userAttributeProvider.getRegisterType(principal)).thenReturn(UserTestFactory.REGISTER_TYPE);
+		when(userAttributeProvider.getEmailAddress(principal)).thenReturn(UserTestFactory.EMAIL_ADDRESS);
+		when(userAttributeProvider.getAddress("")).thenReturn(UserTestFactory.ADDRESS);
+		when(userAttributeProvider.getTrustLevel(principal)).thenReturn(UserTestFactory.TRUST_LEVEL);
+		when(userAttributeProvider.getUnknownAttributes(principal)).thenReturn(Collections.emptyList());
+	}
+
+	@Test
+	void shouldGetId() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getId()).isEqualTo(UserTestFactory.USER_ID);
+	}
+
+	@Test
+	void shouldGetUsername() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getUsername()).isEqualTo(UserTestFactory.USER_NAME);
+	}
+
+	@Test
+	void shouldGetCompanyName() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getCompanyName()).isEqualTo(UserTestFactory.COMPANY_NAME);
+	}
+
+	@Test
+	void shouldGetLegalForm() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getLegalForm()).isEqualTo(UserTestFactory.LEGAL_FORM);
+	}
+
+	@Test
+	void shouldGetLegalFormText() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getLegalFormText()).isEqualTo(UserTestFactory.LEGAL_FORM_TEXT);
+	}
+
+	@Test
+	void shouldGetRegisterNumber() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getRegisterNumber()).isEqualTo(UserTestFactory.REGISTER_NUMBER);
+	}
+
+	@Test
+	void shouldGetRegisterType() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getRegisterType()).isEqualTo(UserTestFactory.REGISTER_TYPE);
+	}
+
+	@Test
+	void shouldGetEmailAddress() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getEmailAddress()).isEqualTo(UserTestFactory.EMAIL_ADDRESS);
+	}
+
+	@Test
+	void shouldGetAddress() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getAddress()).isEqualTo(UserTestFactory.ADDRESS);
+	}
+
+	@Test
+	void shouldGetTrustLevel() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getTrustLevel()).isEqualTo(UserTestFactory.TRUST_LEVEL);
+	}
+
+	@Test
+	void shouldGetUnknownAttributes() {
+		var user = userMapper.map(auth);
+
+		assertThat(user.getUnknownAttributes()).isEmpty();
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf7ad0ae5b93d41395e5e5ef1b05a7baca2cdd5d
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTest.java
@@ -0,0 +1,17 @@
+package de.ozgcloud.fachstelle.security;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class UserTest {
+
+	@Test
+	void getAuthorities() {
+		var user = UserTestFactory.create();
+
+		assertThat(user.getAuthorities()).isNotNull();
+		assertThat(user.getAuthorities().iterator().next().getAuthority()).isEqualTo(DefaultRole.ROLE);
+	}
+
+}
\ No newline at end of file
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTestFactory.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..43b6a17f65f611b9a697d0db9fa5d8fbb685fff1
--- /dev/null
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/security/UserTestFactory.java
@@ -0,0 +1,45 @@
+package de.ozgcloud.fachstelle.security;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+
+public class UserTestFactory {
+
+	public static final String USER_ID = UUID.randomUUID().toString();
+	public static final String USER_NAME = USER_ID;
+	public static final String COMPANY_NAME = "Pauls Unternehmen";
+	public static final String LEGAL_FORM = "123";
+	public static final String LEGAL_FORM_TEXT = "AG";
+	public static final String REGISTER_NUMBER = "123";
+	public static final String REGISTER_TYPE = "ABC";
+	public static final String EMAIL_ADDRESS = "paul.panter@test.com";
+	public static final String STREET = "Musterstraße";
+	public static final String HOUSE_NUMBER = "1";
+	public static final String POSTAL_CODE = "11011";
+	public static final String CITY = "Berlin";
+	public static final String COUNTRY = "DE";
+	public static final String ADDRESS = String.format("%s %s, %s %s, %s", STREET, HOUSE_NUMBER, POSTAL_CODE, CITY, COUNTRY);
+	public static final String TRUST_LEVEL = "substantial";
+	public static final String SAML_TOKEN = "samlToken";
+
+	public static User create() {
+		return createBuilder().build();
+	}
+
+	static User.UserBuilder createBuilder() {
+		return new User.UserBuilder()
+		  .id(USER_ID)
+		  .username(USER_NAME)
+		  .companyName(COMPANY_NAME)
+		  .legalForm(LEGAL_FORM)
+		  .legalFormText(LEGAL_FORM_TEXT)
+		  .registerNumber(REGISTER_NUMBER)
+		  .registerType(REGISTER_TYPE)
+		  .emailAddress(EMAIL_ADDRESS)
+		  .address(ADDRESS)
+		  .trustLevel(TRUST_LEVEL)
+		  .expirationDate(Instant.now().plus(4, ChronoUnit.HOURS));
+	}
+
+}
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangControllerITCase.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangControllerITCase.java
index 25cdc6be9e2d212f70e12a0b0052985db07e7fd3..0ed6913cbddbf71737d7f805e35b7994b986e914 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangControllerITCase.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangControllerITCase.java
@@ -51,17 +51,19 @@ import lombok.SneakyThrows;
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false, printOnlyOnFailure = false)
 @TestPropertySource(properties = {
-  "ozgcloud.fachstelle.logout-success-url=http://logout",
-  "ozgcloud.fachstelle.login-redirect-url=http://login",
-  "ozgcloud.fachstelle.cors=http://login;http://saml-idp",
-  "ozgcloud.fachstellen-proxy.base-url=http://proxy",
-  "spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
-  "spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
-  "spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
+		"ozgcloud.fachstelle.logout-success-url=http://logout",
+		"ozgcloud.fachstelle.login-redirect-url=http://login",
+		"ozgcloud.fachstelle.cors=http://login;http://saml-idp",
+		"ozgcloud.fachstellen-proxy.base-url=http://proxy",
+		"keycloak.auth-server-url=http://keycloak",
+		"keycloak.realm=fachstelle",
+		"spring.security.saml2.relyingparty.registration.muk.entity-id=http://mock-idp",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.signing.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].private-key-location=classpath:/mujina-test.key",
+		"spring.security.saml2.relyingparty.registration.muk.decryption.credentials[0].certificate-location=classpath:/mujina-test.crt",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.singlesignon.sign-request=false",
+		"spring.security.saml2.relyingparty.registration.muk.assertingparty.metadata-uri=classpath:/metadata.xml"
 })
 class VorgangControllerITCase {
 
@@ -144,10 +146,10 @@ class VorgangControllerITCase {
 		@SneakyThrows
 		private ResultActions performRequest() {
 			return mockMvc.perform(
-			  get(VorgangController.PATH + "/" + VorgangTestFactory.ID + "?" + PARAM_COLLABORATION_MANAGER_ADDRESS + "="
-				+ COLLABORATION_MANAGER_ADDRESS)
-				.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
-				.with(csrf().asHeader()));
+					get(VorgangController.PATH + "/" + VorgangTestFactory.ID + "?" + PARAM_COLLABORATION_MANAGER_ADDRESS + "="
+							+ COLLABORATION_MANAGER_ADDRESS)
+							.contentType(MediaType.APPLICATION_JSON).characterEncoding(Charset.defaultCharset())
+							.with(csrf().asHeader()));
 		}
 
 	}
diff --git a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteServiceTest.java b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteServiceTest.java
index f12a8ae760314c74eb3e9fe27d53883fbb813bd5..36b8bd4123f14609bf394c7e77155a2a7ed44ccc 100644
--- a/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteServiceTest.java
+++ b/fachstelle-server/src/test/java/de/ozgcloud/fachstelle/vorgang/VorgangRemoteServiceTest.java
@@ -23,10 +23,11 @@
  */
 package de.ozgcloud.fachstelle.vorgang;
 
-import static de.ozgcloud.fachstelle.vorgang.VorgangRemoteService.*;
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
+import de.ozgcloud.fachstelle.FachstellenProxyProperties;
+import de.ozgcloud.fachstelle.common.errorhandling.SamlTokenNotFoundException;
+import de.ozgcloud.fachstelle.proxy.CollaborationGrpcFindVorgangResponse;
+import de.ozgcloud.fachstelle.security.CurrentUserService;
+import de.ozgcloud.fachstelle.security.UserTestFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -41,103 +42,104 @@ import org.springframework.web.client.RestClient;
 import org.springframework.web.client.RestClientException;
 import org.springframework.web.util.UriTemplate;
 
-import de.ozgcloud.fachstelle.FachstellenProxyProperties;
-import de.ozgcloud.fachstelle.proxy.CollaborationGrpcFindVorgangResponse;
+import static de.ozgcloud.fachstelle.vorgang.VorgangRemoteService.FIND_VORGANG_URI;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.Mockito.*;
 
 @ExtendWith(MockitoExtension.class)
 class VorgangRemoteServiceTest {
 
-	@Spy
-	@InjectMocks
-	private VorgangRemoteService vorgangRemoteService;
-
-	@Mock
-	private RestClient fachstellenProxyClient;
+    @Spy
+    @InjectMocks
+    private VorgangRemoteService vorgangRemoteService;
 
-	@Mock
-	private FachstellenProxyProperties fachstellenProxyProperties;
+    @Mock
+    private RestClient fachstellenProxyClient;
 
-	@Mock
-	private VorgangMapper vorgangMapper;
+    @Mock
+    private FachstellenProxyProperties fachstellenProxyProperties;
 
-	@Nested
-	class TestFindVorgang {
+    @Mock
+    private VorgangMapper vorgangMapper;
 
-		private static final String COLLABORATION_MANAGER_ADDRESS_HEADER = "some-header";
-		private static final String COLLABORATION_MANAGER_ADDRESS = VorgangWithCollaborationManagerAddressTestFactory.COLLABORATION_MANAGER_ADDRESS;
+    @Nested
+    class TestFindVorgang {
 
-		private RestClient.RequestHeadersSpec headersSpec;
-		private RestClient.ResponseSpec responseSpec;
-		private ResponseEntity responseEntity;
+        private static final String COLLABORATION_MANAGER_ADDRESS_HEADER = "some-header";
+        private static final String COLLABORATION_MANAGER_ADDRESS = VorgangWithCollaborationManagerAddressTestFactory.COLLABORATION_MANAGER_ADDRESS;
 
-		@BeforeEach
-		void init() {
-			var uriSpec = mock(RestClient.RequestHeadersUriSpec.class);
-			headersSpec = mock(RestClient.RequestHeadersSpec.class);
-			responseSpec = mock(RestClient.ResponseSpec.class);
-			responseEntity = mock(ResponseEntity.class);
+        private RestClient.RequestHeadersSpec headersSpec;
+        private RestClient.ResponseSpec responseSpec;
+        private ResponseEntity responseEntity;
 
-			doReturn(FIND_VORGANG_URI).when(vorgangRemoteService).buildFindVorgangUri(VorgangTestFactory.ID);
-			when(fachstellenProxyProperties.getCollaborationManagerAddressHeader()).thenReturn(COLLABORATION_MANAGER_ADDRESS_HEADER);
-			when(fachstellenProxyClient.get()).thenReturn(uriSpec);
-			when(uriSpec.uri(FIND_VORGANG_URI)).thenReturn(headersSpec);
-			when(headersSpec.accept(MediaType.APPLICATION_JSON)).thenReturn(headersSpec);
-			when(headersSpec.header(COLLABORATION_MANAGER_ADDRESS_HEADER, COLLABORATION_MANAGER_ADDRESS)).thenReturn(headersSpec);
-			when(headersSpec.retrieve()).thenReturn(responseSpec);
-		}
+        @BeforeEach
+        void init() {
+            var uriSpec = mock(RestClient.RequestHeadersUriSpec.class);
+            headersSpec = mock(RestClient.RequestHeadersSpec.class);
+            responseSpec = mock(RestClient.ResponseSpec.class);
+            responseEntity = mock(ResponseEntity.class);
 
-		@Test
-		void shouldCallRestClient() {
-			when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
-			when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
+            doReturn(FIND_VORGANG_URI).when(vorgangRemoteService).buildFindVorgangUri(VorgangTestFactory.ID);
+            when(fachstellenProxyProperties.getCollaborationManagerAddressHeader()).thenReturn(COLLABORATION_MANAGER_ADDRESS_HEADER);
+            when(fachstellenProxyClient.get()).thenReturn(uriSpec);
+            when(uriSpec.uri(FIND_VORGANG_URI)).thenReturn(headersSpec);
+            when(headersSpec.accept(MediaType.APPLICATION_JSON)).thenReturn(headersSpec);
+            when(headersSpec.header(COLLABORATION_MANAGER_ADDRESS_HEADER, COLLABORATION_MANAGER_ADDRESS)).thenReturn(headersSpec);
+            when(headersSpec.retrieve()).thenReturn(responseSpec);
+        }
 
-			vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
+        @Test
+        void shouldCallRestClient() {
+            when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
+            when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
 
-			verify(fachstellenProxyClient).get();
-		}
+            vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
 
-		@Test
-		void shouldCallVorgangMapper() {
-			when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
-			when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
+            verify(fachstellenProxyClient).get();
+        }
 
-			vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
+        @Test
+        void shouldCallVorgangMapper() {
+            when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
+            when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
 
-			verify(vorgangMapper).toVorgang(any());
-		}
+            vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
 
-		@Test
-		void shouldGetResponse() {
-			when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
-			when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
-			when(vorgangMapper.toVorgang(any())).thenReturn(VorgangTestFactory.create());
+            verify(vorgangMapper).toVorgang(any());
+        }
 
-			var response = vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
+        @Test
+        void shouldGetResponse() {
+            when(responseSpec.toEntity(CollaborationGrpcFindVorgangResponse.class)).thenReturn(responseEntity);
+            when(responseEntity.getBody()).thenReturn(new CollaborationGrpcFindVorgangResponse());
+            when(vorgangMapper.toVorgang(any())).thenReturn(VorgangTestFactory.create());
 
-			assertThat(response).isNotNull();
-		}
+            var response = vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS);
 
-		@Test
-		void shouldThrowException() {
-			when(headersSpec.retrieve()).thenThrow(new RestClientException("some error"));
+            assertThat(response).isNotNull();
+        }
 
-			assertThatExceptionOfType(RestClientException.class).isThrownBy(
-			  () -> vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS));
-		}
+        @Test
+        void shouldThrowException() {
+            when(headersSpec.retrieve()).thenThrow(new RestClientException("some error"));
 
-	}
+            assertThatExceptionOfType(RestClientException.class).isThrownBy(
+                    () -> vorgangRemoteService.findVorgang(VorgangTestFactory.ID, COLLABORATION_MANAGER_ADDRESS));
+        }
 
-	@Nested
-	class TestBuildFindVorgangUri {
+    }
 
-		@Test
-		void shouldReturnVorgangUri() {
-			final String expectedUri = new UriTemplate(FIND_VORGANG_URI).expand(VorgangTestFactory.ID, "--dummy-saml-token--").toString();
-			final String uri = vorgangRemoteService.buildFindVorgangUri(VorgangTestFactory.ID);
+    @Nested
+    class TestBuildFindVorgangUri {
 
-			assertThat(uri).isEqualTo(expectedUri);
-		}
+        @Test
+        void shouldReturnVorgangUri() {
+            final String expectedUri = new UriTemplate(FIND_VORGANG_URI).expand(VorgangTestFactory.ID, VorgangRemoteService.DUMMY_SAML_TOKEN).toString();
+            final String uri = vorgangRemoteService.buildFindVorgangUri(VorgangTestFactory.ID);
 
-	}
+            assertThat(uri).isEqualTo(expectedUri);
+        }
+    }
 
-}
\ No newline at end of file
+}