Skip to content

Authentication

Moritz Roessler edited this page Apr 25, 2023 · 3 revisions

Authentication

Server Side

Authentication is straightforward with React Server. React Server provides an authenticate util to check the headers for a valid jwt token.

All you need to do to authenticate a user is calling authenticate in your serverside component and if the user doesn't have a valid Bearer token in the headers authorization, rendering will throw an error.

backend/src/components/Session

import { authenticate, isClientContext } from '@state-less/react-server';
import { ServerSideProps } from './ServerSideProps';
import { JWT_SECRET } from '../config';

export const Session = (props, { context = { headers: {} } }) => {
    const { headers } = context;

    if (!isClientContext(context)) {
        return <ServerSideProps session={null} />;
    }

    const session = authenticate(headers, JWT_SECRET);
    return <ServerSideProps session={session} />;
};

Client Side

React-client comes with an AuthenticationProvider which you can use to simplify the authentication process.

To get started, wrap your App in an AuthenticationProvider. This provider exposes an authenticate function that needs to be called with a strategy and a data option.

The data required depends on the strategy used to authenticate the current user.

For instance, with a Google OAuth login, you need to pass AuthStrategy.Google as the strategy, and data expects an object with {accessToken, idToken}. Both can be obtained by performing a client-side OAuth login with Google.

To implement a Google OAuth login, you can use the library react-google-login, which provides a <GoogleLogin /> component that you can use to set up your client-side OAuth flow.

Here's an example of how to log in and obtain a valid session from React Server using react-google-login and authenticate from @state-less/react-client:

import { Avatar, Button } from '@mui/material';
import { authContext } from '@state-less/react-client';
import { useContext } from 'react';
import GoogleLogin from 'react-google-login';
import GoogleIcon from '@mui/icons-material/Google';

const logError = (response) => {
  console.log(response);
};

export const LoggedInGoogleButton = (props) => {
  const { session, authenticate } = useContext(authContext);

  if (session.strategy !== 'google') {
    return null;
  }

  const decoded = session.strategies.google.decoded;
  return (
    <Button color="secondary">
      <Avatar
        src={decoded.picture}
        sx={{ width: 24, height: 24, mr: 1 }}
      ></Avatar>
      {decoded.name}
    </Button>
  );
};

export const GoogleLoginButton = () => {
  const { session, authenticate } = useContext(authContext);

  return session?.strategy === 'google' ? (
    <LoggedInGoogleButton />
  ) : (
    <GoogleLogin
      clientId="534678949355-odq15l4236372p864f63ci14g794sfqf.apps.googleusercontent.com"
      buttonText="Login"
      onSuccess={({ accessToken, tokenId }) => {
        console.log('Authenticating with google');
        authenticate({
          strategy: 'google',
          data: { accessToken, idToken: tokenId },
        });
      }}
      render={(props) => {
        return (
          <Button color="secondary" {...props}>
            <GoogleIcon sx={{ mr: 1 }} />
            Login
          </Button>
        );
      }}
      onFailure={logError}
      cookiePolicy={'single_host_origin'}
    />
  );
};

As demonstrated, it only requires a single call to authenticate to log in to React Server, offering a versatile approach for logging in with various providers.

To consume the backend session from your client, you can use useComponent.

frontend/src/server-components/Session.tsx

import { Alert } from '@mui/material';
import { useComponent } from '@state-less/react-client';

export const ServerSession = () => {
  const [component, { error, loading }] = useComponent('session', {});
  if (loading) return <Alert severity="info">Loading...</Alert>;
  if (error) return <Alert severity="error">{error.message}</Alert>;
  return (
    <Alert severity="success">{`Currently logged in on the server as ${sessionInfo(
      component.props.session
    )}`}</Alert>
  );
};

export const googleInfo = (session) => {
  return `${session.strategies.google.decoded.name} (${session.strategies.google.decoded.email})`;
};

const handler = {
  google: googleInfo,
};

export const sessionInfo = (session) => {
  return handler[session.strategy](session);
};

Provider

In order for the authenticate call to succeed, you need to wrap your App in an AuthProvder. The provider exposes information about the current session and brokers authentication with the server. The AuthenticationProvider needs to be wrapped in an ApolloProvider or be given an apollo client instance.

import { AuthProvider } from '@state-less/react-client';

const App = () => {
  <ApolloProvider client={localClient}>
    <AuthProvider>  
      <Main />
    </AuthProvider>
  </ApolloProvider>
}

This is it, you can now enjoy authenticating with the server.