flow
flow copied to clipboard
Cannot navigate to secured View when not logged for OAuth2
Description of the bug
When using RouterLink and Anchor to a secured @Route with @PermitAll an error page is displayed when not logged in.
Could not navigate to '
Expected behavior
A login page should be show. Azure call it user flow: https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview.
Adding router-ignore makes it work. I have added router-ignore to the /logout link which is not a Vaadin route.
Minimal reproducible example
It is the same behavior for com.azure.spring:spring-cloud-azure-starter-active-directory-b2c:4.3.0 and org.springframework.boot:spring-boot-starter-oauth2-client:2.6.7. For azure b2c the config is:
@EnableWebSecurity
public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter {
private final AadB2cOidcLoginConfigurer configurer;
public SecurityConfig(AadB2cOidcLoginConfigurer configurer) {
this.configurer = configurer;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.apply(configurer);
}
}
@Route("vaadin-hello")
@PermitAll()
public class VaadinHelloView extends VerticalLayout {
public VaadinHelloView() {
add(new H1("Hello from VAADIN"));
Versions
Vaadin: 23.2.0.alpha3 Flow: 23.2.0.alpha2 Java: JetBrains s.r.o. 17.0.2 OS: amd64 Windows 10 10.0 Browser: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Live reload: Java active (HotswapAgent): Front end active IntelliJ Tomcat
That is expected. Any link since v23 that has no router-ignore
attribute is handled by the Vaadin Router. It's documented in the Migration Guide.
That is expected. Any link since v23 that has no
router-ignore
attribute is handled by the Vaadin Router. It's documented in the Migration Guide.
It is a router link I am talking about. A add(new RouterLink("RouterLink to VaadinHelloView", VaadinHelloView.class)); to @Route("vaadin-hello") shown above
This sounds like you have the logic to show a login form based on the URL that is requested on the HTTP level, rather than also being based on Vaadin views that bypass HTTP URLs because of how Vaadin uses a SPA architecture.
What you need is to also call setLoginView
in your VaadinWebSecurityConfigurerAdapter
, as shown in https://vaadin.com/docs/latest/security/enabling-security/#security-configuration-class.
For more information about the difference between HTTP-level URLs and Vaadin view URLs, you can read my blog post: https://vaadin.com/blog/the-dangers-of-using-the-wrong-abstraction-for-vaadin-access-control
What you need is to also call
setLoginView
in yourVaadinWebSecurityConfigurerAdapter
, as shown in https://vaadin.com/docs/latest/security/enabling-security/#security-configuration-class.
Not sure how to do this. I do not have a login view, it is handled by azure user-flow.
There is most likely a "login URL" managed by the Spring Security integration. In our example with Google login, it's /oauth2/authorization/google
but I'm not directly familiar with what it would be in your case.
If you cannot find the corresponding URL for your case, then you might also do a workaround through adding a dummy login view on the Vaadin side that only triggers are page reload (using ui.getPage().reload()
). When the page is reloaded, it will cause a HTTP-level request to a forbidden URL which will be intercepted by the regular Spring Security integration and in that way forward the user to the actual login page. The drawback of this approach is that it might cause an additional redirect for the user compared to if they could directly be sent to the right location.
I can use
setLoginView(http, "/oauth2/authorization/B2C_1_signupsignin1");
but I am not redirected back to the link I clicked.
Tried the ui.getPage().reload()
, did not work for me.
Found a solution for this to work after many hours. After login the page-link I clicked is shown.
It is a ugly hack. I think this should be solved in the Vaadin framework.
@svein-loken Keep in mind that this usage of executeJs leaves you open for attacks. You should convert this to the usage shown here: https://vaadin.com/docs/latest/security/advanced-topics/vulnerabilities/#running-custom-javascript
@knoobie Thanks! I updated my post.
I investigated this issue with more detail. If I create a Vaadin application from start.vaadin.com with two views, one that is public and the other one secured, and if I add a link from the public page to the secured page, when the user clicks on the link, they are redirected to the login page, and after entering the credentials then they are redirected to the secured page. This works just fine and you can test it with this project: secured-app.zip
If I modify that project just a little bit to add support for oauth2 authentication (I'm attaching also this project:
secured-app-oauth2.zip, for testing it, you have to follow the step 1 of this tutorial and then modify the application.properties
as explained in step 3) then the behavior is as follows: when the user clicks on the link they are shown a page that says:
Could not navigate to 'hello'
Available routes:
<root>
hello
login
This detailed message is only shown when running in development mode.
This is a wrong behavior, the server is not allowing to navigate to that page, but is not redirecting to the login page like in the plain login example. This, of course, can be avoided by using router-ignore as explained by @knoobie earlier, but it is not a perfect solution (you have to remember to do that to every single link in public pages that target internal pages and if you forgot to do that it is hard for a new developer to realize what the problem is). The server side should figure out about the issue and send a redirect to the configured login page. If you just refresh the "Could not navigate to 'hello'" page, it will redirect you to the google login page.
Tried @mlopezFC solution to handle RouteNotFoundError, but then it hides normal navigation error. My best solution is so far:
If I modify that project just a little bit to add support for oauth2 authentication (I'm attaching also this project: secured-app-oauth2.zip
So if you take this app and tell Vaadin about where the login view is, using setLoginView(http, "/oauth2/authorization/google");
then it will redirect to the login view also when navigating
If I modify that project just a little bit to add support for oauth2 authentication (I'm attaching also this project: secured-app-oauth2.zip
So if you take this app and tell Vaadin about where the login view is, using
setLoginView(http, "/oauth2/authorization/google");
then it will redirect to the login view also when navigating
This is something we tried initially - but the hole point of this issue is that user will not see the selected page.
Related issues:
-
#14528. This probably is the root cause here
-
#14520. More of a cosmetic issue
Then it seems like the success handler is applied by default only formLogin
which has no shared configuration with oauth2Login
so you would need something like
oauth2Login.successHandler(getVaadinSavedRequestAwareAuthenticationSuccessHandler(http));
private VaadinSavedRequestAwareAuthenticationSuccessHandler getVaadinSavedRequestAwareAuthenticationSuccessHandler(
HttpSecurity http) {
VaadinSavedRequestAwareAuthenticationSuccessHandler vaadinSavedRequestAwareAuthenticationSuccessHandler = new VaadinSavedRequestAwareAuthenticationSuccessHandler();
vaadinSavedRequestAwareAuthenticationSuccessHandler
.setDefaultTargetUrl(applyUrlMapping(""));
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
vaadinSavedRequestAwareAuthenticationSuccessHandler
.setRequestCache(requestCache);
}
return vaadinSavedRequestAwareAuthenticationSuccessHandler;
}
to get the same configuration
Tried but it is not working for me
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {
public static final String LOGOUT_URL = "/";
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private final AadB2cOidcLoginConfigurer configurer;
public SecurityConfiguration(AadB2cOidcLoginConfigurer configurer) {
this.configurer = configurer;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.apply(configurer);
super.configure(http);
setLoginView(http, "/oauth2/authorization/B2C_1_signupsignin1");
http.oauth2Login().successHandler(getVaadinSavedRequestAwareAuthenticationSuccessHandler(http));
}
private VaadinSavedRequestAwareAuthenticationSuccessHandler getVaadinSavedRequestAwareAuthenticationSuccessHandler(HttpSecurity http) {
VaadinSavedRequestAwareAuthenticationSuccessHandler vaadinSavedRequestAwareAuthenticationSuccessHandler
= new VaadinSavedRequestAwareAuthenticationSuccessHandler();
vaadinSavedRequestAwareAuthenticationSuccessHandler.setDefaultTargetUrl(applyUrlMapping(""));
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
vaadinSavedRequestAwareAuthenticationSuccessHandler.setRequestCache(requestCache);
}
return vaadinSavedRequestAwareAuthenticationSuccessHandler;
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.ignoring().antMatchers("/images/*.png");
}
}
What about looking at my solution https://github.com/vaadin/flow/issues/14253#issuecomment-1212918821? Maybe introduce a method setExternalLogin(true)?
I would recommend you to create an Azure Active Directory B2C tenant for testing https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant. Then it will also work for Azure AD for company/B2B. It is free up to 50,000 MAU https://azure.microsoft.com/en-us/pricing/details/active-directory/external-identities/
This ticket/PR has been released with Vaadin 23.3.0.alpha1 and is also targeting the upcoming stable 23.3.0 version.
This ticket/PR has been released with Vaadin 23.2.3.
Tried both 23.3.0.alpha1 and 23.2.3 but it does not work. Created a sample: https://github.com/sveine/vaadin-azure-ad-b2c-not-working-example
It is pretty easy to set up and it is free:
https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory-b2c-oidc
Thanks for testing @sveine , I re-open the ticket, we'll retest it with your example and get back to you.
@sveine can you please try call setOAuth2LoginPage(HttpSecurity http, String oauth2LoginPage)
in yourcustom SecurityConfig class, to inform ViewAccessChecker
about the correct login path ?
@sveine can you please try call
setOAuth2LoginPage(HttpSecurity http, String oauth2LoginPage)
in yourcustom SecurityConfig class, to informViewAccessChecker
about the correct login path ?
I don't have a login page. It is defined by the flow, which works with "router-ignore". I tried this anyway:
Try to use setOAuth2LoginPage(). Now /logout does not work https://github.com/sveine/vaadin-azure-ad-b2c-not-working-example/commit/754a1c8cb9d25d1c79e6226bf93d05897d8984f2
@sveine I tried to run the application with azure AD B2C configured as documented in the link you provided and both links to the secured view works as expected (redirect to the login form or show the view if user is authenticated).
For logout, by default, spring LoginFilter
processes only POST requests on the logout URL, if CSRF is configured (and VaadinWebSecurity
does it).
To make it work, you can do it programmatically with a button as explained in https://vaadin.com/docs/latest/security/enabling-security/#log-out-capability, or, if you want to have a direct link (GET request) you should provide a logoutRequestMatcher
to LogoutConfigurer
, for example
http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
Please let me know if you still experience issues or if I missed something.
The logoutRequestMatcher() and /logout works, but I am not getting the logout flow from Azure. I think the user should get this page as it is a "standard". Maybe Azure is clearing up something also. Is there any configuration that allow the logout flow to be shown on /logout?
The logout button is not working well. Try running my last check in. It is in VaadinHelloView()
In the end I think the SecurityConfig class configuration is to magical. Initially a link with router-ignore
did work with the simple configuration in my first commit. It is similar to this: https://github.com/Azure-Samples/azure-spring-boot-samples/blob/spring-cloud-azure_v4.4.0/aad/spring-cloud-azure-starter-active-directory-b2c/aad-b2c-web-application/src/main/java/com/azure/spring/sample/aad/b2c/security/WebSecurityConfiguration.java
Sorry, what do you mean by "not getting the logout flow from Azure"? What do you expect to see after pressing the logout link? I couldn't see anything related to logout in the links you provided.
I think he is talking about something like RP-Initiated logout from the Oauth Spec - which is not part of flow and has to be implemented by himself.
Thanks, @knoobie. I think the same but asked to be sure
Check out rev 194d0c8285ff3be8dce7e14dd3e5e45670583970, "Not working in starter flow 23.2.3", click in the link "RouterLink to VaadinHelloView (router-ignore)", then logout. You will se this page:
This is to be expected - see https://stackoverflow.com/a/58199008/1662997