{"_id":"@7x7cl/remix-auth","name":"@7x7cl/remix-auth","dist-tags":{"latest":"3.7.1"},"versions":{"3.7.1":{"name":"@7x7cl/remix-auth","version":"3.7.1","description":"Simple Authentication for Remix","keywords":["remix","auth","authentication","local","auth0","oauth2","strategies"],"homepage":"https://github.com/sergiodxa/remix-auth#readme","repository":{"type":"git","url":"git+https://github.com/sergiodxa/remix-auth.git"},"license":"MIT","author":{"name":"Sergio Xalambrí","email":"hello@sergiodxa.com","url":"https://sergiodxa.com"},"main":"./build/index.js","types":"./build/index.d.ts","scripts":{"build":"tsc --project tsconfig.json","coverage":"npm run test -- --coverage","lint":"eslint --ext .ts,.tsx src/","test":"jest --config=config/jest.config.ts --passWithNoTests","typecheck":"tsc --project tsconfig.json --noEmit"},"dependencies":{"react-router":"^7.0.0","uuid":"^8.3.2"},"devDependencies":{"@babel/core":"^7.14.2","@babel/preset-env":"^7.14.1","@babel/preset-react":"^7.13.13","@babel/preset-typescript":"^7.13.0","@react-router/node":"^7.0.0","@react-router/serve":"^7.0.0","@types/jest":"^29.5.5","@types/prop-types":"^15.7.4","@types/react":"^18.2.20","@types/uuid":"^8.3.3","@typescript-eslint/eslint-plugin":"^6.7.3","@typescript-eslint/parser":"^6.7.3","babel-jest":"^26.6.3","eslint":"^7.26.0","eslint-config-prettier":"^8.3.0","eslint-plugin-cypress":"^2.11.3","eslint-plugin-import":"^2.22.1","eslint-plugin-jest":"^24.3.6","eslint-plugin-jest-dom":"^3.9.0","eslint-plugin-prettier":"^3.4.0","eslint-plugin-promise":"^5.1.0","eslint-plugin-testing-library":"^4.3.0","eslint-plugin-unicorn":"^32.0.1","jest":"^29.7.0","jest-fetch-mock":"^3.0.3","prettier":"^2.3.2","react":"^18.2.0","ts-node":"^9.1.1","typescript":"^5.1.6"},"peerDependencies":{"react-router":"^7.0.0"},"_id":"@7x7cl/remix-auth@3.7.1","gitHead":"ebcfeb80687c6a3be506e55ba4caf683a67e4d30","bugs":{"url":"https://github.com/sergiodxa/remix-auth/issues"},"_nodeVersion":"22.11.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-6vrvKIMHT8H0t6Tl/xuyh+k6NRbyGG2uHwu7l4yBTmyKPIklr5KHUrHahq3DMWFGAXmfoGEPUjK0L0uOYtZ8UQ==","shasum":"83d2f0fdf3884d97470115f00bfeee9d385975dc","tarball":"https://registry.npmjs.org/@7x7cl/remix-auth/-/remix-auth-3.7.1.tgz","fileCount":13,"unpackedSize":37955,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIB8zODy907k77eWYxUVw4myN6o4zAvwXC/b0IgVoJxtxAiEAgiz1HMkB+unBXcTXnLbhEuPQP1YYN+BhxIo1Gsv/gts="}]},"_npmUser":{"name":"7x7cl","email":"esteban@7x7.cl"},"directories":{},"maintainers":[{"name":"7x7cl","email":"esteban@7x7.cl"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/remix-auth_3.7.1_1732490634355_0.05926503932292859"},"_hasShrinkwrap":false}},"time":{"created":"2024-11-24T23:23:53.933Z","3.7.1":"2024-11-24T23:23:54.563Z","modified":"2024-11-24T23:23:54.843Z"},"maintainers":[{"name":"7x7cl","email":"esteban@7x7.cl"}],"description":"Simple Authentication for Remix","homepage":"https://github.com/sergiodxa/remix-auth#readme","keywords":["remix","auth","authentication","local","auth0","oauth2","strategies"],"repository":{"type":"git","url":"git+https://github.com/sergiodxa/remix-auth.git"},"author":{"name":"Sergio Xalambrí","email":"hello@sergiodxa.com","url":"https://sergiodxa.com"},"bugs":{"url":"https://github.com/sergiodxa/remix-auth/issues"},"license":"MIT","readme":"![](/assets/header.png)\n\n# Remix Auth\n\n### Simple Authentication for [Remix](https://remix.run/)\n\n## Features\n\n- Full **Server-Side** Authentication\n- Complete **TypeScript** Support\n- **Strategy**-based Authentication\n- Easily handle **success and failure**\n- Implement **custom** strategies\n- Supports persistent **sessions**\n\n## Overview\n\nRemix Auth is a complete open-source authentication solution for Remix.run applications.\n\nHeavily inspired by [Passport.js](https://passportjs.org), but completely rewrote it from scratch to work on top of the [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). Remix Auth can be dropped in to any Remix-based application with minimal setup.\n\nAs with Passport.js, it uses the strategy pattern to support the different authentication flows. Each strategy is published individually as a separate npm package.\n\n## Installation\n\nTo use it, install it from npm (or yarn):\n\n```bash\nnpm install remix-auth\n```\n\nAlso, install one of the strategies. A list of strategies is available in the [Community Strategies discussion](https://github.com/sergiodxa/remix-auth/discussions/111).\n\n## Usage\n\nRemix Auth needs a session storage object to store the user session. It can be any object that implements the [SessionStorage interface from Remix](https://remix.run/docs/en/main/utils/sessions#createsessionstorage).\n\nIn this example I'm using the [createCookieSessionStorage](https://remix.run/docs/en/main/utils/sessions#createcookiesessionstorage) function.\n\n```ts\n// app/services/session.server.ts\nimport { createCookieSessionStorage } from \"@remix-run/node\";\n\n// export the whole sessionStorage object\nexport let sessionStorage = createCookieSessionStorage({\n  cookie: {\n    name: \"_session\", // use any name you want here\n    sameSite: \"lax\", // this helps with CSRF\n    path: \"/\", // remember to add this so the cookie will work in all routes\n    httpOnly: true, // for security reasons, make this cookie http only\n    secrets: [\"s3cr3t\"], // replace this with an actual secret\n    secure: process.env.NODE_ENV === \"production\", // enable this in prod only\n  },\n});\n\n// you can also export the methods individually for your own usage\nexport let { getSession, commitSession, destroySession } = sessionStorage;\n```\n\nNow, create a file for the Remix Auth configuration. Here import the `Authenticator` class and your `sessionStorage` object.\n\n```ts\n// app/services/auth.server.ts\nimport { Authenticator } from \"remix-auth\";\nimport { sessionStorage } from \"~/services/session.server\";\n\n// Create an instance of the authenticator, pass a generic with what\n// strategies will return and will store in the session\nexport let authenticator = new Authenticator<User>(sessionStorage);\n```\n\nThe `User` type is whatever you will store in the session storage to identify the authenticated user. It can be the complete user data or a string with a token. It is completely configurable.\n\nAfter that, register the strategies. In this example, we will use the [FormStrategy](https://github.com/sergiodxa/remix-auth-form) to check the documentation of the strategy you want to use to see any configuration you may need.\n\n```ts\nimport { FormStrategy } from \"remix-auth-form\";\n\n// Tell the Authenticator to use the form strategy\nauthenticator.use(\n  new FormStrategy(async ({ form }) => {\n    let email = form.get(\"email\");\n    let password = form.get(\"password\");\n    let user = await login(email, password);\n    // the type of this user must match the type you pass to the Authenticator\n    // the strategy will automatically inherit the type if you instantiate\n    // directly inside the `use` method\n    return user;\n  }),\n  // each strategy has a name and can be changed to use another one\n  // same strategy multiple times, especially useful for the OAuth2 strategy.\n  \"user-pass\"\n);\n```\n\nNow that at least one strategy is registered, it is time to set up the routes.\n\nFirst, create a `/login` page. Here we will render a form to get the email and password of the user and use Remix Auth to authenticate the user.\n\n```tsx\n// app/routes/login.tsx\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"@remix-run/node\";\nimport { Form } from \"@remix-run/react\";\nimport { authenticator } from \"~/services/auth.server\";\n\n// First we create our UI with the form doing a POST and the inputs with the\n// names we are going to use in the strategy\nexport default function Screen() {\n  return (\n    <Form method=\"post\">\n      <input type=\"email\" name=\"email\" required />\n      <input\n        type=\"password\"\n        name=\"password\"\n        autoComplete=\"current-password\"\n        required\n      />\n      <button>Sign In</button>\n    </Form>\n  );\n}\n\n// Second, we need to export an action function, here we will use the\n// `authenticator.authenticate method`\nexport async function action({ request }: ActionFunctionArgs) {\n  // we call the method with the name of the strategy we want to use and the\n  // request object, optionally we pass an object with the URLs we want the user\n  // to be redirected to after a success or a failure\n  return await authenticator.authenticate(\"user-pass\", request, {\n    successRedirect: \"/dashboard\",\n    failureRedirect: \"/login\",\n  });\n};\n\n// Finally, we can export a loader function where we check if the user is\n// authenticated with `authenticator.isAuthenticated` and redirect to the\n// dashboard if it is or return null if it's not\nexport async function loader({ request }: LoaderFunctionArgs) {\n  // If the user is already authenticated redirect to /dashboard directly\n  return await authenticator.isAuthenticated(request, {\n    successRedirect: \"/dashboard\",\n  });\n};\n```\n\nWith this, we have our login page. If we need to get the user data in another route of the application, we can use the `authenticator.isAuthenticated` method passing the request this way:\n\n```ts\n// get the user data or redirect to /login if it failed\nlet user = await authenticator.isAuthenticated(request, {\n  failureRedirect: \"/login\",\n});\n\n// if the user is authenticated, redirect to /dashboard\nawait authenticator.isAuthenticated(request, {\n  successRedirect: \"/dashboard\",\n});\n\n// get the user or null, and do different things in your loader/action based on\n// the result\nlet user = await authenticator.isAuthenticated(request);\nif (user) {\n  // here the user is authenticated\n} else {\n  // here the user is not authenticated\n}\n```\n\nOnce the user is ready to leave the application, we can call the `logout` method inside an action.\n\n```ts\nexport async function action({ request }: ActionFunctionArgs) {\n  await authenticator.logout(request, { redirectTo: \"/login\" });\n};\n```\n\n## Advanced Usage\n\n### Custom redirect URL based on the user\n\nSay we have `/dashboard` and `/onboarding` routes, and after the user authenticates, you need to check some value in their data to know if they are onboarded or not.\n\nIf we do not pass the `successRedirect` option to the `authenticator.authenticate` method, it will return the user data.\n\nNote that we will need to store the user data in the session this way. To ensure we use the correct session key, the authenticator has a `sessionKey` property.\n\n```ts\nexport async function action({ request }: ActionFunctionArgs) {\n  let user = await authenticator.authenticate(\"user-pass\", request, {\n    failureRedirect: \"/login\",\n  });\n\n  // manually get the session\n  let session = await getSession(request.headers.get(\"cookie\"));\n  // and store the user data\n  session.set(authenticator.sessionKey, user);\n\n  // commit the session\n  let headers = new Headers({ \"Set-Cookie\": await commitSession(session) });\n\n  // and do your validation to know where to redirect the user\n  if (isOnboarded(user)) return redirect(\"/dashboard\", { headers });\n  return redirect(\"/onboarding\", { headers });\n};\n```\n\n### Changing the session key\n\nIf we want to change the session key used by Remix Auth to store the user data, we can customize it when creating the `Authenticator` instance.\n\n```ts\nexport let authenticator = new Authenticator<AccessToken>(sessionStorage, {\n  sessionKey: \"accessToken\",\n});\n```\n\nWith this, both `authenticate` and `isAuthenticated` will use that key to read or write the user data (in this case, the access token).\n\nIf we need to read or write from the session manually, remember always to use the `authenticator.sessionKey` property. If we change the key in the `Authenticator` instance, we will not need to change it in the code.\n\n### Reading authentication errors\n\nWhen the user cannot authenticate, the error will be set in the session using the `authenticator.sessionErrorKey` property.\n\nWe can customize the name of the key when creating the `Authenticator` instance.\n\n```ts\nexport let authenticator = new Authenticator<User>(sessionStorage, {\n  sessionErrorKey: \"my-error-key\",\n});\n```\n\nFurthermore, we can read the error using that key after a failed authentication.\n\n```ts\n// in the loader of the login route\nexport async function loader({ request }: LoaderFunctionArgs) {\n  await authenticator.isAuthenticated(request, {\n    successRedirect: \"/dashboard\",\n  });\n  let session = await getSession(request.headers.get(\"cookie\"));\n  let error = session.get(authenticator.sessionErrorKey);\n  return json({ error }, {\n    headers:{\n      'Set-Cookie': await commitSession(session) // You must commit the session whenever you read a flash\n    }\n  });\n};\n```\n\nRemember always to use the `authenticator.sessionErrorKey` property. If we change the key in the `Authenticator` instance, we will not need to change it in the code.\n\n### Errors Handling\n\nBy default, any error in the authentication process will throw a Response object. If `failureRedirect` is specified, this will always be a redirect response with the error message on the `sessionErrorKey`.\n\nIf a `failureRedirect` is not defined, Remix Auth will throw a 401 Unauthorized response with a JSON body containing the error message. This way, we can use the CatchBoundary component of the route to render any error message.\n\nIf we want to get an error object inside the action instead of throwing a Response, we can configure the `throwOnError` option to `true`. We can do this when instantiating the `Authenticator` or calling `authenticate`.\n\nIf we do it in the `Authenticator,` it will be the default behavior for all the `authenticate` calls.\n\n```ts\nexport let authenticator = new Authenticator<User>(sessionStorage, {\n  throwOnError: true,\n});\n```\n\nAlternatively, we can do it on the action itself.\n\n```ts\nimport { AuthorizationError } from \"remix-auth\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n  try {\n    return await authenticator.authenticate(\"user-pass\", request, {\n      successRedirect: \"/dashboard\",\n      throwOnError: true,\n    });\n  } catch (error) {\n    // Because redirects work by throwing a Response, you need to check if the\n    // caught error is a response and return it or throw it again\n    if (error instanceof Response) return error;\n    if (error instanceof AuthorizationError) {\n      // here the error is related to the authentication process\n    }\n    // here the error is a generic error that another reason may throw\n  }\n};\n```\n\nIf we define both `failureRedirect` and `throwOnError`, the redirect will happen instead of throwing an error.\n","readmeFilename":"README.md"}