This week's giveaway is in the Java in General forum.
We're giving away four copies of Java Challengers and have Rafael del Nero on-line!
See this thread for details.
Win a copy of Java Challengers this week in the Java in General forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Paul Clapham
  • Ron McLeod
  • paul wheaton
  • Devaka Cooray
Sheriffs:
  • Jeanne Boyarsky
  • Tim Cooke
  • Liutauras Vilda
Saloon Keepers:
  • Tim Moores
  • Tim Holloway
  • Stephan van Hulst
  • Carey Brown
  • Piet Souris
Bartenders:
  • salvin francis
  • Mikalai Zaikin
  • Himai Minh

Custom FORM authentication in Jakarta EE 8: can't leave login page after authentication

 
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Cross-posted: https://stackoverflow.com/questions/62354490/custom-form-authentication-in-jakarta-ee-8-cant-leave-login-page-after-authent

- Java 11
- Jakarta EE 8
- Wildfly 20

I decided to give a try to the new authentication mechanism called "Custom FORM" authentication from Java EE Security that I saw in BalusC's book The Definitive Guide To JSF in Java EE 8.

I created a simple application to test it and it's not working. The problem is that after a successful authentication, I'm not redirected to the welcome page, I keep in the login page. It's as if the application didn't recognize that I'm authenticated.

These are the relevant parts of the relevant files:

web.xml



UserAuthenticator.java (performs a simple authentication)



PagLogin (the CDI bean)



login.xhtml (the JSF login page)



But when I click on the Enter button I saw that I really get an AuthenticationStatus.SUCCESS, but I'm not redirected to the start.xhtml page, even if I try to change it in the browser address bar manually. It keeps me redirecting to the login page.

After if I put this code:



The variable authenticated is true.

Maybe there's just a little thing missing, but I don't know what it is.

Does anyone know what's going on here?

UPDATE:

I noticed that I also get this warn in the application server log after login:



I don't know if this has something to do with the problem.
 
Saloon Keeper
Posts: 12878
279
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Welcome to CodeRanch Marcos!

Can you show us the page that you're trying to redirect to after login?
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Welcome to CodeRanch Marcos!

Can you show us the page that you're trying to redirect to after login?



Of course. This is the page:

start.xhtml (it is just a test page)


 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I changed from @CustomFormAuthenticationMechanismDefinition to @FormAuthenticationMechanismDefinition and the application worked, the start.xhtml page was finally displayed. Of course, I also had to change the login.xhtml page to use j_security_check. Even thought the application is working now, I'm not on control of the authentication mechanism anymore. But that's ok to me.
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I decided to give another try to the Custom Form authentication mechanism and made some progress.

Here are the relevant parts of the relevant files:

web.xml:


CustomFormAuthenticationConfig.java:


UserAuthenticator.java:


login.xhtml:


and finally

Login.java:


So, as the comments on the authenticateUser() method shows, the problem is that after a successful login (username and password correct), executeUserAuthentication() returns AuthenticationStatus.SEND_CONTINUE, and not AuthenticationStatus.SUCCESS. As a result, the code in the AuthenticationStatus.SUCCESS branch is not executed.

Hope to get your feedback about this problem as I'm almost sure it's a bug in Wildfly. If not, I'd like to know what I'm doing wrong.

Thank you in advance.
 
Saloon Keeper
Posts: 23517
161
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Welcome to the Ranch, Marcos, but oh, that's awful.

In the original J2EE you could not login via logic request, only by having the user request a secured URL. In that situation, the URL request would be parked and a login dialog or form would be presented, after which - if the user logged in OK, the original URL was processed. The login process is handled completely by the webapp server and is totally transparent to the webapp.

JEE added a method by which a login could be done via program, logic. That makes web pages with login controls possible, although that should be done carefully, since you are routing sensitive data through the application, whereas in traditional login, the userid/password never reached the application at all.

It's now time for me to pontificate Rule #1 for JavaServer Faces. That is: The more JSF internal methods and data structures you are using in your application code, the more likely you're doing it wrong.

I can handle on-page logins entirely in JSF with only the lightest escape to javax.faces resources. If memory serves, the login method hangs off the HttpServletRequest, so I'd have to obtain that object from the FacesContext. I make it a practice to put all my methods that need to access FacesContext in their own private utility class so that I don't have a lot of framework-dependent code splattered all over my app (see Rule #1) and I can swap out a mock class for offline testing.

So let's say that I have JSFUtils.login(username, pasword) as my application login service, where my JSFUtils class gets the HttpServletRequest and invokes its login method. Yes, it's a static method. One of the few cases where static is better than an instance method.

I can then collapse the login process down to an h:form element for the login credentials and optional Submit button (DON'T put any other controls in this form!). Remember also, that HTML, and thus JSF forms may not be nested.

Then I have a login backing bean (managed bean) that hold the userid and password as bean properties (with their set/get methods) and the following action method:


The JSFUtils login method makes only minimal use of the FacesContext - all it does is retrieve the HttpServletRequest object from the FacesContext and then invokes its login method. The code is a lot simpler, since I'm letting the system do most of the work. As it expects to. No need for any of the javax.security stuff at all. The container handles it automatically, which is simpler, more flexible and more secure.

You can also define a logical "welcome" navigation target to keep from hard-coding an absolute resource path (/welcome.jsf). It's a matter of preference.

Personally, I don't like being forced to a welcome page, because it interferes with my ability to bookmark site pages. But that's me.

Finally, if you need to determine whether a user is already logged in, check the HttpServetRequest getRemoteUser() or getUserPrincipal() methods. The return null unless the user is logged in. So I also usually define a JSFUtils.getUserId() method.
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello, Tim.

Thank you for your answer and advices.

I can assure you that after the executeUserAuthentication() method, the user is really authenticated.

That's the code of start.xhtml (the welcome page):



And that's the code of Start.java (the backing bean):



I already checked and the variable user is not null, which means a successful authentication. But the AuthenticationStatus.SEND_CONTINUE problem remains.

I have this little application as a zip file, tried to upload it here for you to test it, but I'm not allowed to upload zip files.
 
Tim Holloway
Saloon Keeper
Posts: 23517
161
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'm not saying your logic didn't work. But it didn't "work" in that you didn't get what you wanted.

What I am saying is that JSF was designed to avoid framework-specific code and objects wherever possible. An "ideal" JSF app wouldn't be using any javax.faces resources except for the model classes (DataTable and SelectItem). Failing that, as I said, there are ways to do it that require minimal user logic and a greatly reduced need to reference anything that starts with "javax". Which is to say, platform dependency. JSF was also designed as a POJO framework so that application components could be used in non-JSF, and in fact, in non-web applications.

I'm suspecting however that your main problem is that your login form isn't a stand-alone form. Either that or the use of low-level services in place of the high-level ones is messing up the JSF lifecycle flow.

It's very important that an on-page login be on a JSF h:form within an h:body element.. Nothing unrelated to the login should be in that same form, since JSF will reject any input that doesn't validate. If a user half-fills a data form, then decides to login, they can find themselves mysteriously blocked. And again, forms cannot be nested within other forms. It's HTML that forces that, not JSF. So make sure you've got the view template properly defined.

If you follow JSF best practices, I assure you that you'll get where you want to go with no trouble at all. I've done so myself. A login form is handled just the same as any other form, and the fact that it's invoking a login service instead of, say. fetching or updating a database makes no difference to how it navigates.
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Regarding my login form, Tim, I think it's really a stand-alone one.

Here's the whole story:

layout.xhtml:



login.xhtml:



errors.xhtml (tag):



script.xhtml (tag):



stylesheet.xhtml (tag):



Login.java (again):



That's it, there's nothing less, nothing more.
 
Tim Holloway
Saloon Keeper
Posts: 23517
161
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I regret to day that that's more detail than I'm willing to go through considering I don't get paid for this, but it does look like there is at least the possibility that you could mix non-login controls on a login form or illegally nest another form as part of the content of that component.

But either of those two cases would be more likely to block authentication if there were invalid values on the form and process if the form data (including non-login data) was all valid.

The bigger problem is that your processing logic is WAY over-complicated. The code sample I gave earlier is the sum total of the JSF logic required for me to login AND redirect, excepting the short method in JSFUtils that gets the HttpServletRequest and invokes its login method. Nothing else. No grabbing FacesContext and forcing redirects, no javax.seccurity, nada. The JSF framework is intelligent enough to do all that.

In fact, that's one of the biggest problems that JSF novices have with JSF. They're so used to working with frameworks where you have to call all sorts of API methods to get things done. JSF is an Inversion of Control framework, and that means that most of the API work is done by the framework and the user code just supplies the data, business logic and navigation rule values to use using ordinary JavaBean techniques.
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:
The bigger problem is that your processing logic is WAY over-complicated



With all humility, my processing logic is REALLY NOT complicated. It's resumed to 2 methods in the Login class (only 1 if you inline the executeUserAuthentication() in the authenticateUser() method) and the class UserAuthenticator.

Also, lots of people use the same code that I'm using. It's even the recommended way to execute authentication nowdays in Jakarta EE, not low level things. After all, what's the use of abstraction if I have to use low level code? I'm not using any proprietary code. I'm using only code from the Jakarta EE APIs, which, as far as I know, is portable.

https://github.com/Apress/definitive-guide-to-jsf-javaee8 (Chapter 13: Security. First time I saw the code I'm using. It's in this book)
https://rieckpil.de/howto-simple-form-based-authentication-for-jsf-2-3-with-java-ee-8-security-api/
https://github.com/Pilpin/mwe-jakartasecurity
 
Marcos AntonioPS
Greenhorn
Posts: 11
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
By the way, I have just cloned this simple application just to make sure I wasn't doing anything wrong

https://github.com/rieckpil/blog-tutorials/tree/master/jsf-simple-login-with-java-ee-security-api

that is the same application of this post

https://rieckpil.de/howto-simple-form-based-authentication-for-jsf-2-3-with-java-ee-8-security-api/

and it shows the same behaviour of my application. In this method of the class LoginBacking:



the continueAuthentication() method returns AuthenticationStatus.SEND_CONTINUE, and not AuthenticationStatus.SUCCESS.

This is the continueAuthentication() method:



It enforces my feeling that there's a bug in the Wildfly implementation of the Jakarta EE specification. The problem is not only with my application.

You can also clone this application and test it for yourself. I'm running it in Wildfly 21.
 
Tim Holloway
Saloon Keeper
Posts: 23517
161
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I don't think that your authenticator logic is the recommended way to login webapps. Webapps run in a pre-existing security context, they don't build one up from scratch. And two methods with lots of lines of code and references to half-a-dozen javax packages is definitely more complicated than 2 methods each of which runs only about 8 lines of code and referenced something like 2 JEE classes (javax.faces.FacesContext and javax.servlet.http.HttpServletRequest).

Using JEE API classes doesn't make code "portable". It just locks them into JEE. And depending on the API used, locks them further into one of the many corners of JEE.

But the telling thing is that with all your code, it doesn't work, and it's consumed something like 5 months. The approach I'm promoting is something that does work. And it has worked ever since J2EE became JEE.
 
Tim Holloway
Saloon Keeper
Posts: 23517
161
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Incidentally, I just looked up the link that inspired you. I'm not impressed. It appears to be a manufactured attempt to use a low-level security API instead of the security API that was built into J2EE since about Day 1. I didn't see any explanation as to why it was supposed to be superior to the traditional method, just that it used a fancy API.

Not everything found on the Internet is gold, and even when it is, it may not apply in all cases.
 
You showed up just in time for the waffles! And this tiny ad:
Building a Better World in your Backyard by Paul Wheaton and Shawn Klassen-Koop
https://coderanch.com/wiki/718759/books/Building-World-Backyard-Paul-Wheaton
reply
    Bookmark Topic Watch Topic
  • New Topic