Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 89 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Implement the session-backed challenge nonce store as follows:
import org.springframework.beans.factory.ObjectFactory;
import eu.webeid.security.challenge.ChallengeNonce;
import eu.webeid.security.challenge.ChallengeNonceStore;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSession;

public class SessionBackedChallengeNonceStore implements ChallengeNonceStore {

Expand Down Expand Up @@ -134,36 +134,103 @@ import eu.webeid.security.validator.AuthTokenValidatorBuilder;
...
```

## 6. Add a REST endpoint for issuing challenge nonces
## 6. Add a filter for issuing challenge nonces

A REST endpoint that issues challenge nonces is required for authentication. The endpoint must support `GET` requests.
Request Filters that issue challenge nonces for regular Web eID and Web eID for Mobile authentication flows are required for authentication.
The filters must support POST requests.

In the following example, we are using the [Spring RESTful Web Services framework](https://spring.io/guides/gs/rest-service/) to implement the endpoint, see also the full implementation [here](example/blob/main/src/main/java/eu/webeid/example/web/rest/ChallengeController.java).
The `WebEidChallengeNonceFilter` handles `/auth/challenge` requests and issues a new nonce for regular Web eID authentication flow.
See the full implementation [here](example/src/main/java/eu/webeid/example/security/WebEidChallengeNonceFilter.java).

```java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import eu.webeid.security.challenge.ChallengeNonceGenerator;
...
public final class WebEidChallengeNonceFilter extends OncePerRequestFilter {
private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writer();
private final RequestMatcher requestMatcher;
private final ChallengeNonceGenerator nonceGenerator;

public WebEidChallengeNonceFilter(String path, ChallengeNonceGenerator nonceGenerator) {
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
this.nonceGenerator = nonceGenerator;
}

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain chain
) throws ServletException, IOException {
if (!requestMatcher.matches(request)) {
chain.doFilter(request, response);
return;
}

var dto = new ChallengeDTO(nonceGenerator.generateAndStoreNonce().getBase64EncodedNonce());

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
OBJECT_WRITER.writeValue(response.getWriter(), dto);
}

public record ChallengeDTO(String nonce) {}
}
```

@RestController
@RequestMapping("auth")
public class ChallengeController {
Similarly, the `WebEidMobileAuthInitFilter` handles `/auth/mobile/init` requests for Web eID for Mobile authentication flow by generating a challenge nonce and returning a deep link URI. This deep link contains both the challenge nonce and a login URI for the mobile authentication flow.
See the full implementation [here](example/src/main/java/eu/webeid/example/security/WebEidMobileAuthInitFilter.java).

@Autowired // for brevity, prefer constructor dependency injection
private ChallengeNonceGenerator nonceGenerator;
```java
public final class WebEidMobileAuthInitFilter extends OncePerRequestFilter {
private static final ObjectWriter OBJECT_WRITER = new ObjectMapper().writer();
private final RequestMatcher requestMatcher;
private final ChallengeNonceGenerator nonceGenerator;
private final String loginPath;

public WebEidMobileAuthInitFilter(String path, String loginPath, ChallengeNonceGenerator nonceGenerator) {
this.requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST, path);
this.nonceGenerator = nonceGenerator;
this.loginPath = loginPath;
}

@GetMapping("challenge")
public ChallengeDTO challenge() {
// a simple DTO with a single 'nonce' field
final ChallengeDTO challenge = new ChallengeDTO();
challenge.setNonce(nonceGenerator.generateAndStoreNonce().getBase64EncodedNonce());
return challenge;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain chain
) throws IOException, ServletException {
if (!requestMatcher.matches(request)) {
chain.doFilter(request, response);
return;
}

var challenge = nonceGenerator.generateAndStoreNonce();

String loginUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path(loginPath).build().toUriString();

String payloadJson = OBJECT_WRITER.writeValueAsString(
new AuthPayload(challenge.getBase64EncodedNonce(), loginUri)
);
String encoded = Base64.getEncoder().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
String eidAuthUri = "web-eid-mobile://auth#" + encoded;

response.setContentType(MediaType.APPLICATION_JSON_VALUE);
OBJECT_WRITER.writeValue(response.getWriter(), new AuthUri(eidAuthUri));
}

record AuthPayload(String challenge, @JsonProperty("login_uri") String loginUri) {}
record AuthUri(@JsonProperty("auth_uri") String authUri) {}
}
```

Both filters are registered in the Spring Security filter chain in ApplicationConfiguration
See the full implementation [here](example/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java):
```java
http
.addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator),
UsernamePasswordAuthenticationFilter.class);
```

Also, see general guidelines for implementing secure authentication services [here](https://github.com/SK-EID/smart-id-documentation/wiki/Secure-Implementation-Guide).

## 7. Implement authentication
Expand All @@ -172,11 +239,11 @@ Authentication consists of calling the `validate()` method of the authentication

When using [Spring Security](https://spring.io/guides/topicals/spring-security-architecture) with standard cookie-based authentication,

- implement a custom authentication provider that uses the authentication token validator for authentication as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/AuthTokenDTOAuthenticationProvider.java),
- implement a custom authentication provider that uses the authentication token validator for authentication as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java),
- implement an AJAX authentication processing filter that extracts the authentication token and passes it to the authentication manager as shown [here](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAjaxLoginProcessingFilter.java),
- configure the authentication provider and authentication processing filter in the application configuration as shown [here](example/blob/main/src/main/java/eu/webeid/example/config/ApplicationConfiguration.java).

The gist of the validation is [in the `authenticate()` method](example/blob/main/src/main/java/eu/webeid/example/security/AuthTokenDTOAuthenticationProvider.java#L74-L76) of the authentication provider:
The gist of the validation is [in the `authenticate()` method](example/blob/main/src/main/java/eu/webeid/example/security/WebEidAuthenticationProvider.java#L74-L76) of the authentication provider:

```java
try {
Expand Down
13 changes: 8 additions & 5 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ This repository contains the code of a minimal Spring Boot web application that
- Spring Security,
- the Web eID authentication token validation library [_web-eid-authtoken-validation-java_](https://github.com/web-eid/web-eid-authtoken-validation-java),
- the Web eID JavaScript library [_web-eid.js_](https://github.com/web-eid/web-eid.js),
- the digital signing library [_DigiDoc4j_](https://github.com/open-eid/digidoc4j).
- the digital signing library [_DigiDoc4j_](https://github.com/open-eid/digidoc4j),
- the Android application [_MOPP-Android_](https://github.com/open-eid/MOPP-Android/).

The project uses Maven for managing the dependencies and building the application. Maven project configuration file `pom.xml` is in the root of the project.

Expand All @@ -113,11 +114,13 @@ The source code folder `src` contains the application source code and resources
The `src/main/java/eu/webeid/example` directory contains the Spring Boot application Java class and the following subdirectories:

- `config`: Spring and HTTP security configuration, Web eID authentication token validation library configuration, trusted CA certificates loading etc,
- `security`: Web eID authentication token validation library integration with Spring Security via an `AuthenticationProvider` and `AuthenticationProcessingFilter`,
- `security`: Web eID authentication token validation library integration with Spring Security
- `AuthenticationProvider` and `AuthenticationProcessingFilter` for handling Web eID authentication tokens,
- `WebEidChallengeNonceFilter` for issuing the challenge nonce required by the authentication flow,
- `WebEidMobileAuthInitFilter` for issuing the challenge nonce and generating the deep link with the authentication request, used to initiate the mobile authentication flow,
- `WebEidAjaxLoginProcessingFilter` and `WebEidLoginPageGeneratingFilter` for handling login requests.
- `service`: Web eID signing service implementation that uses DigiDoc4j, and DigiDoc4j runtime configuration,
- `web`: Spring Web MVC controller for the welcome page and Spring Web REST controllers that provide endpoints
- for getting the challenge nonce used by the authentication token validation library,
- for digital signing.
- `web`: Spring Web MVC controller for the welcome page and Spring Web REST controller that provides a digital signing endpoint.

The `src/resources` directory contains the resources used by the application:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@

package eu.webeid.example.config;

import eu.webeid.example.security.AuthTokenDTOAuthenticationProvider;
import eu.webeid.example.security.WebEidAjaxLoginProcessingFilter;
import eu.webeid.example.security.WebEidAuthenticationProvider;
import eu.webeid.example.security.WebEidChallengeNonceFilter;
import eu.webeid.example.security.WebEidMobileAuthInitFilter;
import eu.webeid.example.security.ui.WebEidLoginPageGeneratingFilter;
import eu.webeid.security.challenge.ChallengeNonceGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
Expand All @@ -34,29 +38,32 @@
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class ApplicationConfiguration implements WebMvcConfigurer {
public class ApplicationConfiguration {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthTokenDTOAuthenticationProvider authTokenDTOAuthenticationProvider, AuthenticationConfiguration authConfig) throws Exception {
public SecurityFilterChain filterChain(
HttpSecurity http,
WebEidAuthenticationProvider webEidAuthenticationProvider,
AuthenticationConfiguration authConfig,
ChallengeNonceGenerator challengeNonceGenerator
) throws Exception {
return http
.authenticationProvider(authTokenDTOAuthenticationProvider)
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.logout(logout -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.build();
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/files/**", "/img/**", "/js/**", "/scripts/**").permitAll()
.requestMatchers("/").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(webEidAuthenticationProvider)
.addFilterBefore(new WebEidMobileAuthInitFilter("/auth/mobile/init", "/auth/mobile/login", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new WebEidChallengeNonceFilter("/auth/challenge", challengeNonceGenerator), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new WebEidLoginPageGeneratingFilter("/auth/mobile/login", "/auth/login"), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new WebEidAjaxLoginProcessingFilter("/auth/login", authConfig.getAuthenticationManager()), UsernamePasswordAuthenticationFilter.class)
.logout(l -> l.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
.headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.build();
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/welcome").setViewName("welcome");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SameSiteCookieConfiguration implements WebMvcConfigurer {
public class SameSiteCookieConfiguration {

@Bean
public TomcatContextCustomizer configureSameSiteCookies() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies("strict");
// Set to "strict" if Web eID for Mobile flow is not used - this would restrict sending back the
// authentication response in the Web eID for Mobile flow.
cookieProcessor.setSameSiteCookies("lax");
context.setCookieProcessor(cookieProcessor);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

package eu.webeid.example.config;

import eu.webeid.example.security.SessionBackedChallengeNonceStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* SOFTWARE.
*/

package eu.webeid.example.config;
package eu.webeid.example.security;

import org.springframework.beans.factory.ObjectFactory;
import eu.webeid.security.challenge.ChallengeNonce;
Expand Down
Loading