Optare v1.0 is now available. Get started →
Quickstarts
Remix

Add Login to Your Remix App

This quickstart shows how to integrate Optare with Remix.

Prerequisites

  • Node.js 18+ installed
  • An Optare account
  • A Remix project

1. Install the SDK

npm install @optare/optareid-js jose

2. Get Your Credentials

  1. Log in to Optare Console (opens in a new tab)
  2. Create an OAuth client:
    • Type: Regular Web Application
    • Redirect URI: https://yourapp.com/auth/callback
  3. Copy Client ID and Client Secret

3. Configure Environment

Create .env:

OPTARE_DOMAIN=https://id.optare.one
OPTARE_CLIENT_ID=your_client_id
OPTARE_CLIENT_SECRET=your_client_secret
SESSION_SECRET=your_session_secret

4. Create Session Utility

Create app/utils/session.server.ts:

import { createCookieSessionStorage } from '@remix-run/node';
 
export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: '__session',
    httpOnly: true,
    maxAge: 60 * 60 * 24 * 30,
    path: '/',
    sameSite: 'lax',
    secrets: [process.env.SESSION_SECRET!],
    secure: process.env.NODE_ENV === 'production',
  },
});

5. Create Auth Routes

Create app/routes/auth.login.tsx:

import { redirect, type LoaderFunctionArgs } from '@remix-run/node';
 
export async function loader({ request }: LoaderFunctionArgs) {
  const params = new URLSearchParams({
    client_id: process.env.OPTARE_CLIENT_ID!,
    redirect_uri: 'https://yourapp.com/auth/callback',
    response_type: 'code',
    scope: 'openid profile email organization',
  });
 
  return redirect(
    `${process.env.OPTARE_DOMAIN}/oauth/authorize?${params}`
  );
}

Create app/routes/auth.callback.tsx:

import { redirect, type LoaderFunctionArgs } from '@remix-run/node';
import { sessionStorage } from '~/utils/session.server';
 
export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');
 
  if (!code) {
    return redirect('/login?error=no_code');
  }
 
  // Exchange code for tokens
  const tokenResponse = await fetch(
    `${process.env.OPTARE_DOMAIN}/oauth/token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code,
        client_id: process.env.OPTARE_CLIENT_ID,
        client_secret: process.env.OPTARE_CLIENT_SECRET,
        redirect_uri: 'https://yourapp.com/auth/callback',
      }),
    }
  );
 
  const tokens = await tokenResponse.json();
 
  // Store tokens in session
  const session = await sessionStorage.getSession();
  session.set('access_token', tokens.access_token);
  session.set('refresh_token', tokens.refresh_token);
 
  return redirect('/dashboard', {
    headers: {
      'Set-Cookie': await sessionStorage.commitSession(session),
    },
  });
}

6. Protect Routes

Create app/utils/auth.server.ts:

import { redirect } from '@remix-run/node';
import * as jose from 'jose';
import { sessionStorage } from './session.server';
 
export async function requireAuth(request: Request) {
  const session = await sessionStorage.getSession(
    request.headers.get('Cookie')
  );
  
  const token = session.get('access_token');
  
  if (!token) {
    throw redirect('/auth/login');
  }
 
  // Validate token
  const JWKS = jose.createRemoteJWKSet(
    new URL(`${process.env.OPTARE_DOMAIN}/.well-known/jwks.json`)
  );
 
  try {
    const { payload } = await jose.jwtVerify(token, JWKS, {
      issuer: process.env.OPTARE_DOMAIN,
    });
    return payload;
  } catch {
    throw redirect('/auth/login');
  }
}

7. Use in Loaders

import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { requireAuth } from '~/utils/auth.server';
 
export async function loader({ request }: LoaderFunctionArgs) {
  const user = await requireAuth(request);
  return json({ user });
}
 
export default function Dashboard() {
  const { user } = useLoaderData<typeof loader>();
  
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {user.email}</p>
    </div>
  );
}

Next Steps