Authenticating a User

At stage of the application, a user can register, but they are still unable to sign in.

As with the previous Challenge, the authenticate() method is currently hard coded to accept only the email graphacademy@neo4j.com and password letmein from the fixture users.json.

In this challenge you will rewrite the authenticate() function of the AuthService to do the following:

But first, let’s take a look at how Authentication works in the application. If you prefer, you can skip straight to Implementing Authentication.

Authorization & Authentication

Authorizing users works by checking the email and hashed password against information in the database.

When a user submits the Sign In form from the UI, the following process occurs:

  1. A route handler for the /login route in src/main/java/neoflix/routes/AuthRoutes.java listens for a POST request.

  2. The login route calls the authenticate() method in the AuthService with the username and password from the request.

The authenticate() method performs the following actions:

  1. Attempt to find the user by their email address.

  2. If the user can’t be found, throw a ValidationException.

  3. Compare the encrypted password in the database against the unencrypted password sent with the request.

  4. If the passwords do not match, throw a ValidationException.

  5. Otherwise, return an object containing the user’s safe properties (email, name, userId), and a JWT token with a set of claims that can be used in the UI.

For this strategy to work correctly, the authenticate() method must return an object which represents the user on successful login, or return null or an error if the credentials are incorrect.

Once a user is authorized a JWT token is generated (using the Auth0 JWT library) and passed back to the front-end to be used in the Authorization header until the user signs out or the token expires.

For authenticated users the application uses a pre-processor before() the routes to verify the authentication for each request, which is delegated to: AppUtils.handleAuthAndSetUser.

There the Authorization header is extracted and validated using the Auth0 JWT library. If that is successful the userId is set as a request attribute which can be accessed by the route implementation.

Implementing Authentication

To implement database authentication, you will modify the authenticate method in the AuthService.

java
neoflix/services/AuthService.java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/main/src/main/java/neoflix/services/AuthService.java[tag=authenticate]

Your challenge is to update the authenticate() method to perform the following actions:

Open a new Session

First, open a new session:

java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/main/src/main/java/example/Index.java[tag=session]

Find the User node within a Read Transaction

Use a MATCH query to find a :User node with the email address passed to the method as a parameter.

java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/05-authentication/src/main/java/neoflix/services/AuthService.java[tag="query"]

Verify The User Exists

If no records are returned, you can safely assume that the user does not exist in the database, then the single() method on Result will throw a NoSuchRecordException.

In this case, an ValidationException is thrown.

java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/05-authentication/src/main/java/neoflix/services/AuthService.java[tag="auth-catch"]

Compare Passwords

Next, you must verify that the unencrypted password matches the encrypted password saved as a property against the :User node.

The bcrypt library used to encrypt the password also includes a verify() function that can be used to compare a string against a previously encrypted value, we encapsulate that in AuthUtils.verifyPassword().

If the AuthUtils.verifyPassword() method returns false, the passwords do not match and the method should throw a new ValidationException.

java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/05-authentication/src/main/java/neoflix/services/AuthService.java[tag="password"]

Return User Details

As with the register() method, the UI expects a JWT token to be returned along with the response.

The code is already written to the example, so this can be re-purposed at the end of the method.

java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/05-authentication/src/main/java/neoflix/services/AuthService.java[tag="auth-return"]

Once you have applied these changes to the authenticate() method, scroll to Testing to verify that the method works as expected.

Working Solution

Click here to reveal the completed authenticate() method:
java
neoflix/services/AuthService.java
Unresolved directive in lesson.adoc - include::https://raw.githubusercontent.com/neo4j-graphacademy/llm-vectors-unstructured/05-authentication/src/main/java/neoflix/services/AuthService.java[tag="authenticate"]

Testing

To test that this functionality has been correctly implemented, run the following code in a new terminal session:

sh
Running the test
mvn test -Dtest=neoflix._05_AuthenticationTest#authenticateUser

The test file is located at src/test/java/neoflix/_05_AuthenticationTest.java.

Are you stuck? Click here for help

If you get stuck, you can see a working solution by checking out the 05-authentication branch by running:

sh
Check out the 05-authentication branch
git checkout 05-authentication

You may have to commit or stash your changes before checking out this branch. You can also click here to expand the Support pane.

Verifying the Test

This test creates a new :User node in the database with an email address of authenticated@neo4j.com using the register() method on the AuthService and then attempts to verify the user using the authenticate method.

Hit the Check Database button below to verify that the test has been successfully run.

Hint

At the end of the test, a piece of code finds the User node and sets the authenticatedAt property to the current date and time using the Cypher datetime() function. As long as this value is within the past 24 hours, the test should pass.

You can run the following query to check for the user within the database. If the shouldVerify value returns true, the verification should be successful.

cypher
MATCH (u:User {email: 'authenticated@neo4j.com'})
RETURN u.email, u.authenticatedAt,
    u.authenticatedAt >= datetime() - duration('PT24H') AS shouldVerify

Solution

The following statement will mimic the behaviour of the test, merging a new :User node with the email address authenticated@neo4j.com, assigning a random UUID value to the .userId property and setting an .authenticatedAt property to the current date and time.

cypher
MERGE (u:User {email: "authenticated@neo4j.com"})
SET u.authenticatedAt = datetime()

Once you have run this statement, click Try again…​* to complete the challenge.

Lesson Summary

In this Challenge, you have updated the AuthService to authenticate a User using the data held in the Sandbox database.

In the next Challenge, you will save the current user’s movie ratings to the database.

Chatbot

Hi, I am an Educational Learning Assistant for Intelligent Network Exploration. You can call me E.L.A.I.N.E.

How can I help you today?