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?
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
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
Matti
(Matti Tahvonen)
May 26, 2023, 5:46am
11
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