CDLUC3 / dmsp_frontend_prototype

Repo to test out new NextJS framework
MIT License
0 stars 0 forks source link

Implement Authentication in frontend #19

Open jupiter007 opened 3 months ago

jupiter007 commented 3 months ago

We should start building out how we want to handle authentication on the frontend.

  1. Build placeholder login and signup pages
  2. After the backend returns tokens, save token in cookie securely using 'httponly'
  3. Will need to add middleware to verify token to see if user is authenticated before allowing users to some protected pages
jupiter007 commented 3 months ago

I was able to authenticate with Cognito in the NextJS app, within client-side components.

I created a client-side "sign-up" page using the following packages: "aws-amplify" and "aws-ampfliy/auth".

I created the following amplify.js config component,

import {Amplify} from "aws-amplify";

const awsConfig = {
  Auth: {
    Cognito: {
      userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID,
      region:process.env.NEXT_PUBLIC_REGION,
      userPoolClientId: process.env.NEXT_PUBLIC_POOL_APP_CLIENT_ID,
      loginWith: {
        username: 'true'
      }
    }

  }
} 

export default awsConfig

and configured Amplify with it:

'use client';
import React, {useState, useEffect} from 'react';
import {Amplify} from "aws-amplify";
import {useRouter} from 'next/navigation';
import awsConfig from '@/amplify';
import {signUp,confirmSignUp} from "aws-amplify/auth";
import ConfirmUser from '@/components/confirmation';

Amplify.configure(awsConfig as any)

  const SignUp:React.FC = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [newUser, setNewUser] = useState(false);
    const [verificationCode, setVerificationCode] = useState("");
    const router = useRouter();

    const handleSignUp = async(event: React.FormEvent) => {
      event.preventDefault();
      try {
        const {isSignUpComplete, userId, nextStep} = await signUp({
          username: email,
          password: password,
        })
        console.log("User registered:");
        if(userId) {
          setNewUser(true);
        }
      } catch (error) {
        console.error("Error registering user:", error)
      }
    };

    const handleConfirmSignUp = async (e: React.FormEvent) => {
      e.preventDefault();

      try {
        const response = await confirmSignUp({username:email, confirmationCode: verificationCode});
        console.log(response);
        router.push('/login');
      } catch (err) {
        console.error(err);
      }
    }
    return (
      <main className="bg-gray-200 h-screen flex items-center justify-center">
        {newUser ? <ConfirmUser verificationCode={verificationCode} handleConfirmSignUp={handleConfirmSignUp} setVerificationCode={setVerificationCode} /> :
        <form className="max-w-lg w-full bg-gray-100 shadow-lg p-8 flex flex-col" onSubmit={handleSignUp}>
          <p className="text-xl mb-4 text-center">Create an account</p>

          <label htmlFor="email">Email address:</label>
          <input id="email" type="email" name="email" value={email} className="border py-2 px-4 border-gray-500 focus:outline-none mb-4" onChange={e => setEmail(e.target.value)} required />

          <label htmlFor="password">Password:</label>
          <input id="password" type="password" name="password" value={password} className="border py-2 px-4 border-gray-500 focus:outline-none mb-4" onChange={e => setPassword(e.target.value)} required />

          <button className="mt-3 text-lg font-semibold py-4 px-4 bg-gray-600 text-gray-200" type="submit">Sign Up</button>
        </form>
        }
      </main>
    );
  };
  export default SignUp; 

When I submitted the "sign-up" form, I saw the users appear in AWS Cognito.

Then, in the "login" page, I was able to pass the username and password to Amplify's "signIn" function to authenticate the user.

import { signIn, getCurrentUser, fetchAuthSession } from "aws-amplify/auth";

    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      const email = document.querySelector(".email")
      try{
      // Send the user data to the server to login the user
        const response = await signIn({username: user.email,password: user.password })
        console.log("User logged in:", user);
        console.log(response);

      } catch(e){
      console.log(e)
      }
    };

I also did a test to see that we could obtain the access token from Cognito by using Amplify's "fetchAuthSession":

import { signIn, getCurrentUser, fetchAuthSession } from "aws-amplify/auth";

   useEffect(() => {
      async function getAccessToken() {
        const session = await fetchAuthSession();
        const authToken = session.tokens?.idToken?.toString();
        if(authToken !== undefined){
          setAccessToken(authToken);
        }

      }

We haven't fully decided if we are going with Cognito. Whatever the case, in the final implementation, we will probably want to add an API for handling login and signup, and middleware for handling authorization.

briri commented 3 months ago

Moving to Icebox until we decide if we are using Cognito

jupiter007 commented 1 month ago

We've moved away from using Cognito.

I added some placeholder pages for signup and login here,https://github.com/CDLUC3/dmsp_frontend_prototype/tree/feature/simple-authentication-test, and ran some tests against some updates in the backend to see if I get back the access token. I confirmed that the JWT token was returned.

Now I need to set up a good process for storing the token and returning it in the header with regular and GraphQL requests.

jupiter007 commented 3 weeks ago

This ticket has a lot of updates, so for the purposes of keeping PR reviews shorter, I moved the work to add access tokens to headers in GraphQL requests to this ticket: https://github.com/CDLUC3/dmsp_frontend_prototype/issues/55