This is additional information related to a video walkthrough of a sample Remix application using the remix-auth package which is a passport-like framework for simplifying authentication of your remix application using specific packaged strategies.
In this example, I am using the Form Strategy to show a simple login flow.
After creating your Remix application, install the required npm packages
npm install remix-auth remix-auth-form
Create the app/services/session.server.ts file to manage the session and to hold the type that defines the shape of the session information.
This code is directly from the documentation except for the type User; see documentation linked below for additional information
// app/services/session.server.tsimport{createCookieSessionStorage}from'remix';// export the whole sessionStorage objectexportletsessionStorage=createCookieSessionStorage({cookie:{name:'_session',// use any name you want heresameSite:'lax',// this helps with CSRFpath:'/',// remember to add this so the cookie will work in all routeshttpOnly:true,// for security reasons, make this cookie http onlysecrets:['s3cr3t'],// replace this with an actual secretsecure:process.env.NODE_ENV==='production',// enable this in prod only},});// you can also export the methods individually for your own usageexportlet{getSession,commitSession,destroySession}=sessionStorage;// define the user modelexporttypeUser={name:string;token:string;};
Create an authentication instance in a new file app/services/auth.server.ts. The type User will be introduced when we create the file to manage the session.
import{Authenticator,AuthorizationError}from'remix-auth';import{FormStrategy}from'remix-auth-form';import{sessionStorage,User}from'~/services/session.server';// Create an instance of the authenticator, pass a Type, User, with what// strategies will return and will store in the sessionconstauthenticator=newAuthenticator<User|Error|null>(sessionStorage,{sessionKey:"sessionKey",// keep in syncsessionErrorKey:"sessionErrorKey",// keep in sync});
In the same file we will define the strategy that will be used with this authenticator and return the authenticator object from the module.
the anonymous function related to the strategy could be extracted into a separate function for additional clarity.
We can do some verification inside the function or do it before calling the authenticator. If you are validating in the authenticator, to return errors you must throw them as the type AuthorizationError. These errors can be retrieved from the session using the sessionErrorKey defined when initializing the Authenticator.
If there are no errors then we return whatever information we want stored in the session; in this case it is defined by the type User
// Tell the Authenticator to use the form strategyauthenticator.use(newFormStrategy(async ({form})=>{// get the data from the form...letemail=form.get('email')asstring;letpassword=form.get('password')asstring;// initialize the user hereletuser=null;// do some validation, errors are in the sessionErrorKeyif (!email||email?.length===0)thrownewAuthorizationError('Bad Credentials: Email is required')if (typeofemail!=='string')thrownewAuthorizationError('Bad Credentials: Email must be a string')if (!password||password?.length===0)thrownewAuthorizationError('Bad Credentials: Password is required')if (typeofpassword!=='string')thrownewAuthorizationError('Bad Credentials: Password must be a string')// login the user, this could be whatever process you wantif (email==='aaron@mail.com'&&password==='password'){user={name:email,token:`${password}-${newDate().getTime()}`,};// the type of this user must match the type you pass to the Authenticator// the strategy will automatically inherit the type if you instantiate// directly inside the `use` methodreturnawaitPromise.resolve({...user});}else{// if problem with user throw error AuthorizationErrorthrownewAuthorizationError("Bad Credentials")}}),);exportdefaultauthenticator
Application Routes
There are two routes in this application, the index route which is protected and the login route which is not; we will start with the index route in a file called app/routes/index.ts
Next we need to check before the route is loaded if there is an authenticated user we can load the route, otherwise use redirect to the login route. We can do that using the LoaderFunction and calling the authenticator.isAuthenticated method. If there is an authenticated session then the authenticator.isAuthenticated method will return the session information which we are then passing to the page as loader data.
// app/routes/index.ts/**
* check the user to see if there is an active session, if not
* redirect to login page
*
*/exportletloader:LoaderFunction=async ({request})=>{returnawaitauthenticator.isAuthenticated(request,{failureRedirect:"/login",});};
There is only one action supported in this index route and that is to call the authenticator to log the user out of the application, see the code below.
// app/routes/index.ts/**
* handle the logout request
*
*/exportconstaction:ActionFunction=async ({request})=>{awaitauthenticator.logout(request,{redirectTo:"/login"});};
The last bit of the index route in the actual code to render the component. We use the useLoaderData hook to get the session information we are returning if there is an authenticated session. We then render the user name and token in the page along with a button to logout of the application
// app/routes/index.tsexportdefaultfunctionDashboardPage(){constdata=useLoaderData();return (<divstyle={{fontFamily:"system-ui, sans-serif",lineHeight:"1.4"}}><h1>Welcome to Remix Protected Dashboard</h1><p>{data?.name}{data?.token}</p><Formmethod="post"><button>Log Out</button></Form></div>);}
The second route in this application, the login route is not protected but we don't want render the route if there is already a session; so we use the same authenticator.isAuthenticated method but redirect on success. If not successful, meaning the user is not authenticated, then we are going to render the page. Before rendering the page we check the session, using the LoaderFunction, to see if there are any errors available from the authenticator using the sessionErrorKey, all of this happens in the page's LoaderFunction
// app/routes/login.ts/**
* get the cookie and see if there are any errors that were
* generated when attempting to login
*
*/exportconstloader:LoaderFunction=async ({request})=>{awaitauthenticator.isAuthenticated(request,{successRedirect:"/"});constsession=awaitsessionStorage.getSession(request.headers.get("Cookie"));consterror=session.get("sessionErrorKey");returnjson<any>({error});};
The ActionFunction in the login route is for logging in or authenticating the user.
if successful, we route to the index route, if not we redirect back to login page where the LoaderFunction will determine if there are any associated errors to render in the page.
/**
* called when the user hits button to login
*
*/exportconstaction:ActionFunction=async ({request,context})=>{// call my authenticatorconstresp=awaitauthenticator.authenticate("form",request,{successRedirect:"/",failureRedirect:"/login",throwOnError:true,context,});console.log(resp);returnresp;};
Finally we need to render the actual component page. On the page we have the input form fields for the login, the submit button and a seperate section to render the errors. The information for the errors are returned in the useLoaderData hook and rendered at the bottom of the page.
exportdefaultfunctionLoginPage(){// if i got an error it will come back with the loader dataconstloaderData=useLoaderData();console.log(loaderData);return (<divstyle={{fontFamily:"system-ui, sans-serif",lineHeight:"1.4"}}><h1>Welcome to Remix-Auth Example</h1><p>
Based on the Form Strategy From{""}<ahref="https://github.com/sergiodxa/remix-auth"target={"_window"}>
Remix-Auth Project
</a></p><Formmethod="post"><inputtype="email"name="email"placeholder="email"required/><inputtype="password"name="password"placeholder="password"autoComplete="current-password"/><button>Sign In</button></Form><div>{loaderData?.error?<p>ERROR: {loaderData?.error?.message}</p>:null}</div></div>);}
Remix Authentication Using Remix-Auth Package and the Form Strategy
Remix Authentication Using Remix-Auth Package
#remix #remixrun #reactjs
This is a walkthrough of a sample application using the remix-auth package which is a passport-like framework for simplifying authentication of your remix application using specific packaged strategies.
In this example, I am using the Form Strategy to show a simple login flow