Cloudflare Access to auto login users to vaadin

Hello, i use cloudflare access to give access to the application out of my home.
Cloudflare Access uses an header and cookie to set an jwt.

Now i’m wondering how i can integrate that to the vaadin starter projekt to auto login users that connect through the access tunnel.

Anyone can give me some help on how to do that?

Did you read https://vaadin.com/blog/jwt-authentication-with-vaadin-flow-for-better-developer-and-user-experience ?

yes. But didnt help.
Cloudflare access injects the header and cookie when accessing the app through the tunnel.

So i need a way to read that header or cookie and manually authenticate that user when he comes from the cloudflare tunnel.

Therefore in the token the only thing i need is the user email and then login the user using that email because it got send from cloudflare.
Otherwise i want my user to login using his credentials in vaadin.

You have any idea how i can integrate something like this from the example to get the information when accessing for example “/autologin” to autologin the user with a session as he would if he typed in his credentials?

https://developers.cloudflare.com/cloudflare-one/identity/authorization-cookie/validating-json/#programmatic-verification

https://developers.cloudflare.com/cloudflare-one/identity/authorization-cookie/application-token/

I don’t have a ready made solution by hand, but the keywords “Spring Authentication Filter” should get you pretty far, there you can literally access any Attribute from the Request and e.g. authorize the user and redirect him afterwards

I did it now with the following:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final String JWKS_URL = "https://X.cloudflareaccess.com/cdn-cgi/access/certs"; // replace with your jwks url

    private final AuthenticationContext authenticationContext;
    private final UserDetailsServiceImpl userDetailsService;

    private final UserRepository userRepository;

    public JwtAuthenticationFilter(AuthenticationContext authenticationContext, UserDetailsServiceImpl userDetailsService, UserRepository userRepository) {
        this.authenticationContext = authenticationContext;
        this.userDetailsService = userDetailsService;
        this.userRepository = userRepository;
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if(authenticationContext.isAuthenticated()) {
            filterChain.doFilter(request, response);
            return;
        }

        if(request.getCookies() == null) {
            filterChain.doFilter(request, response);
            return;
        }

        Optional<Cookie> cookie = Arrays.stream(request.getCookies()).filter(cookie1 -> cookie1.getName().equalsIgnoreCase("CF_Authorization")).findFirst();
        if (cookie.isEmpty()) {
            filterChain.doFilter(request, response);
            return;
        }

        try {
            DecodedJWT jwt = JWT.decode(cookie.get().getValue());
            JwkProvider provider = new UrlJwkProvider(new URL(JWKS_URL));
            Jwk jwk = provider.get(jwt.getKeyId());

            Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT decodedJWT = verifier.verify(cookie.get().getValue());

            String email = decodedJWT.getClaim("email").asString();

            if (email != null) {
                Authentication authentication = new UsernamePasswordAuthenticationToken(email.toLowerCase(), null);

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }

        } catch (Exception exception){
            exception.printStackTrace();
        }

        filterChain.doFilter(request, response);
    }
}

and in the SecurityConfig i did this:

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {

    private final UserDetailsServiceImpl userDetailsService;

    private final UserRepository userRepository;

    public SecurityConfiguration(UserDetailsServiceImpl userDetailsService, UserRepository userRepository) {
        this.userDetailsService = userDetailsService;
        this.userRepository = userRepository;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new JwtAuthenticationFilter(getAuthenticationContext(), userDetailsService, userRepository), UsernamePasswordAuthenticationFilter.class);
        http.authorizeHttpRequests().requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll();
        http.authorizeHttpRequests().requestMatchers(new AntPathRequestMatcher("/line-awesome/**/*.svg")).permitAll();

        super.configure(http);

        setLoginView(http, LoginView.class);
    }
}

But now i have the problem, that if im coming from the tunnel it redirects me everytime i reload the page manually to /login and after that to /.

How can i manage that it directs me to /path/before/reloading

I think you are looking for https://www.baeldung.com/spring-security-redirect-login

how can i do it and preserve the vaadin login form? With that solution it shows a completely different login form

the login form looks like that:

@AnonymousAllowed
@PageTitle("Login")
@Route(value = "login")
public class LoginView extends LoginOverlay implements BeforeEnterObserver {

    private final AuthenticatedUser authenticatedUser;

    public LoginView(AuthenticatedUser authenticatedUser) {
        this.authenticatedUser = authenticatedUser;
        setAction(RouteUtil.getRoutePath(VaadinService.getCurrent().getContext(), getClass()));

        LoginI18n i18n = LoginI18n.createDefault();
        i18n.setHeader(new LoginI18n.Header());
        i18n.getHeader().setTitle("LOGIN");
        i18n.getHeader().setDescription("Bitte melden Sie sich an");
        i18n.setAdditionalInformation(null);
        setI18n(i18n);

        setForgotPasswordButtonVisible(false);
        setOpened(true);
    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if (authenticatedUser.get().isPresent()) {
            // Already logged in
            setOpened(false);
            event.forwardTo("");
        }

        setError(event.getLocation().getQueryParameters().getParameters().containsKey("error"));
    }
}

Is there a way to get the path it comes from in the beforeEnter Method?

Technically yes if you work with the underlying http session and store / get the Attribute from there

shouldNotFilter(request);
after authenticating the user manually did the trick

Nice👍 I bet there are also others looking for a similar solution. If you can find a moment, document your findings in an example project or in a blog post!

unfortunately it did the trick only for a few hours, then out of nowhere it stopped working. I now set a cookie that expires in 5 seconds with the url that matches a route (got them using reflections from my views packages and the @Route Annotation) and forward the user to that location in the beforeEnter.

Its not the best solution but it works for my small project and has not that big of a performance impact, maybe if i have time i can get it to work with the filters again