{"id":21805779,"url":"https://github.com/standard-ai/portal","last_synced_at":"2025-04-13T20:03:26.101Z","repository":{"id":47443707,"uuid":"305492969","full_name":"standard-ai/portal","owner":"standard-ai","description":"A drop-in React Login Portal for your projects. It has minimal overhead and it's easy to configure and use in multiple apps.","archived":false,"fork":false,"pushed_at":"2021-08-31T18:15:33.000Z","size":1827,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":8,"default_branch":"main","last_synced_at":"2024-11-20T14:13:18.572Z","etag":null,"topics":["auth","auth0","login","reactjs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/standard-ai.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-19T19:31:51.000Z","updated_at":"2024-01-25T14:19:27.000Z","dependencies_parsed_at":"2022-08-17T14:46:19.963Z","dependency_job_id":null,"html_url":"https://github.com/standard-ai/portal","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/standard-ai%2Fportal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/standard-ai%2Fportal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/standard-ai%2Fportal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/standard-ai%2Fportal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/standard-ai","download_url":"https://codeload.github.com/standard-ai/portal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226745351,"owners_count":17675027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","auth0","login","reactjs"],"created_at":"2024-11-27T12:15:36.976Z","updated_at":"2024-11-27T12:15:37.713Z","avatar_url":"https://github.com/standard-ai.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Standard Portal\n\nA nice drop-in React Login Portal for your projects. It has minimal overhead and it's easy to configure and use in multiple apps. It includes:\n\n- Automatic configuration from environment variables.\n- Shows \"Login Page\" and blocks the App until the user has logged in.\n- Top-right default mini profile once logged in for easy session management.\n- Refresh token on the background and all other nice Auth0 logic included.\n- React Hooks and other API methods and callbacks for customization.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003e\n      Portal home page\n    \u003c/th\u003e\n    \u003cth\u003e\n      Logged-in profile\n    \u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\n      \u003cimg src=\"./assets/home-login.png\" width=\"600px\" /\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      \u003cimg src=\"./assets/profile.gif\" width=\"300px\" /\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nSetup [the `.env` variables](#configuration) and then you only need this code for adding a simple portal:\n\n```js\n// App.js - The code for the screenshot above:\nimport Portal from \"@standard/portal\";\n\n// Just a plain \u003cAuth\u003e wrapper includes a lot of goodies:\nexport default () =\u003e (\n  \u003cPortal\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n## Getting started\n\nFirst install this library. You need to install `styled-components` as well if you don't have it yet:\n\n```bash\nnpm i @standard/portal styled-components\n```\n\nThen provide the Auth0 config as environment variables:\n\n```\n# Find this in Auth0\nREACT_APP_AUTH_CLIENT_ID=\nREACT_APP_AUTH_DOMAIN=\nREACT_APP_AUTH_AUDIENCE=\n```\n\nFinally set it up. Normally just dropping it on your `App.js` it's enough!\n\n```js\n// App.js\nimport Portal from \"@standard/portal\";\n\nexport default () =\u003e (\n  \u003cPortal\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\nThis so far will only provide a plain frontend-only auth. Your data should also locked up behind auth, so you will need to pass the auth token at least to the API. For this you would usually use either the [`\u003cPortal onUser={fn}\u003e...\u003c/Portal\u003e`](#portal) callback, or the [`await getToken()`](#await-gettoken) (recommended) function.\n\n## Configuration\n\nThe main title will be taken from your page `\u003ctitle\u003eHello World\u003c/title\u003e` in the `\u003chead /\u003e`, so no need to do anything there.\n\nThe configuration is automatically read from the **environment variables**:\n\n```\n# Required variables from Auth0:\nREACT_APP_AUTH_CLIENT_ID=\nREACT_APP_AUTH_DOMAIN=\nREACT_APP_AUTH_AUDIENCE=\n\n# Optional variables with their defaults:\nREACT_APP_AUTH_REDIRECT=/\nREACT_APP_AUTH_RESPONSE_TYPE=token id_token\nREACT_APP_AUTH_SCOPE=openid profile email\nREACT_APP_AUTH_RETURN_TO=/\n```\n\n## Styling\n\nTo add styles, you can use the classes `.standard-portal` and `.standard-profile` within `styled components`' `createGlobalStyle`:\n\n```js\nimport Portal from \"@standard/portal\";\nimport { createGlobalStyle } from \"styled-components\";\n\nconst SmallerProfile = createGlobalStyle`\n  .standard-profile {\n    width: 40px;\n    height: 40px;\n    top: 5px;\n  }\n`;\n\nexport default function App() {\n  return (\n    \u003cPortal\u003e\n      \u003cSmallerProfile /\u003e\n      {/* The rest of the App here */}\n    \u003c/Portal\u003e\n  );\n}\n```\n\nYou can edit the main screen CSS through the class `.standard-portal`. For example, let's say we want to add a background [from svgbackgrounds.com](https://www.svgbackgrounds.com/):\n\n```\n.standard-portal {\n  background-color: #ffffff;\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1600 900'%3E%3Cdefs%3E%3ClinearGradient id='a' x1='0' x2='0' y1='1' y2='0'%3E%3Cstop offset='0' stop-color='%230FF'/%3E%3Cstop offset='1' stop-color='%23CF6'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='0' x2='0' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%23F00'/%3E%3Cstop offset='1' stop-color='%23FC0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cg fill='%23FFF' fill-opacity='0' stroke-miterlimit='10'%3E%3Cg stroke='url(%23a)' stroke-width='2'%3E%3Cpath transform='translate(0 0)' d='M1409 581 1450.35 511 1490 581z'/%3E%3Ccircle stroke-width='4' transform='rotate(0 800 450)' cx='500' cy='100' r='40'/%3E%3Cpath transform='translate(0 0)' d='M400.86 735.5h-83.73c0-23.12 18.74-41.87 41.87-41.87S400.86 712.38 400.86 735.5z'/%3E%3C/g%3E%3Cg stroke='url(%23b)' stroke-width='4'%3E%3Cpath transform='translate(0 0)' d='M149.8 345.2 118.4 389.8 149.8 434.4 181.2 389.8z'/%3E%3Crect stroke-width='8' transform='rotate(0 1089 759)' x='1039' y='709' width='100' height='100'/%3E%3Cpath transform='rotate(0 1400 132)' d='M1426.8 132.4 1405.7 168.8 1363.7 168.8 1342.7 132.4 1363.7 96 1405.7 96z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\");\n}\n```\n\nSimilarly, you can style children like:\n\n```css\n.standard-portal h1 {\n  /* ... */\n}\n\n.standard-portal p {\n  /* ... */\n}\n\n.standard-portal button {\n  /* ... */\n}\n```\n\nFinally, you can also style the small profile menu through the class `.standard-profile`:\n\n```css\n.standard-profile {\n  /* Example: make the button a bit smaller */\n  width: 40px;\n  height: 40px;\n\n  /* Example: make it always be on the right size of a 1200px layout  */\n  right: calc(50% - 1200px / 2);\n}\n\n.standard-profile img {\n  /* ... */\n}\n\n.standard-profile p {\n  /* ... */\n}\n\n.standard-profile a {\n  /* ... */\n}\n```\n\n## API\n\nThe library exposes mainly a default export and some helpers:\n\n```js\nimport Portal, { useProfile, getToken, logout } from \"@standard/portal\";\n```\n\n- [`\u003cPortal\u003e\u003c/Portal\u003e`](#portal) is a component that should wrap all of your App.\n  - `onUser={fn}`: method that is called when a user logs in.\n  - `showProfile={true}`: boolean to show/hide the small top-right profile.\n- [`useProfile()`](#useprofile) is a hook that returns the user info: `id`, `name`, `email` and `img`.\n- [`getToken()`](#gettoken) is an async method to get the user Auth token\n- [`logout()`](#logout) is an async method to log the user out and show the login screen again\n- `AuthContext` is an advanced [React Context](https://reactjs.org/docs/context.html) variable that exposes the Provider/Consumer context.\n\n### \\\u003cPortal /\u003e\n\nPortal is a component that should wrap all of your app, normally it'd live inside `App.js`:\n\n```js\nimport Portal from \"@standard/portal\";\n\nexport default () =\u003e (\n  \u003cPortal\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n#### \\\u003cPortal onUser={fn} /\u003e\n\nIt exposes an event prop, `onUser`, for whenever a user logs in. This is useful for e.g. attaching the user to Sentry:\n\n```js\nimport Portal from \"@standard/portal\";\nimport * as Sentry from \"@sentry/browser\";\n\n// Register the user on Sentry\nconst onUser = ({ name, email }, token) =\u003e {\n  Sentry.configureScope(function(scope) {\n    scope.setUser({ username: name, email });\n  });\n};\n\nexport default () =\u003e (\n  \u003cPortal onUser={onUser}\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n#### \\\u003cPortal showProfile={true} /\u003e\n\nThis is a boolean prop (defaults to TRUE) to tell whether to show a small profile on the top-right of the page on load:\n\n\u003cimg src=\"./assets/profile.gif\" width=\"300px\" /\u003e\n\nIf you don't like the default profile, you can set this prop to false and provide your own:\n\n```js\n// App.js\nimport Portal, { useProfile, logout } from \"@standard/portal\";\n\nconst Greeting = () =\u003e {\n  const user = useProfile();\n  return (\n    \u003cdiv className=\"MyCustomProfile\"\u003e\n      \u003cp\u003eHi {user.name}!\u003c/p\u003e\n      \u003cbutton onClick={logout}\u003eLogout\u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default () =\u003e (\n  \u003cPortal showProfile={false}\u003e\n    \u003cGreeting /\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n### useProfile()\n\nA [React Hook]() that returns the user information. It can be used within any children of `\u003cPortal\u003e`:\n\n```js\nconst Welcome = () =\u003e {\n  const { name } = useProfile();\n  return \u003cp\u003eHi {name}! Welcome to our app, please ...\u003c/p\u003e;\n};\n```\n\nIt exports these three variables for the current user:\n\n- `id`: the id provided from the network they linked\n- `name`: the full name\n- `email`: the email of the user\n- `img`: a profile picture (uses gravatar). If gravatar is not set, it'll be an image with the first name's first letter and last name's first letter.\n\n### getToken()\n\nGet the Auth token for the currently logged user. This is useful for e.g. making API calls:\n\n```js\nimport { getToken } from '@standard/portal'\n\nconst async getFriends = (id) =\u003e {\n  const token = await getToken();\n  const res = await fetch(`/users/${id}/friends`, {\n    headers: { Authorization: `Bearer ${token}` }\n  });\n  const friends = await res.json();\n  return friends;\n};\n```\n\nIt will be an instantaneous local read most of the calls, but every once in a while it will trigger a token refresh in the background (fully automated!) and that's why it needs to be `await`'ed.\n\n### logout()\n\nLog the user out. This clears the token and redirects the user to the logout page in Auth0 if needed:\n\n```js\nimport { logout } from \"@standard/portal\";\n\nexport default function LogoutButton() {\n  return \u003cbutton onClick={logout}\u003eLogout\u003c/button\u003e;\n}\n```\n\nIt returns the user to the passed parameter, or what was set in `REACT_APP_AUTH_RETURN_TO` otherwise:\n\n```js\nimport { logout } from \"@standard/portal\";\n\nconst returnTo = \"/bye\";\n\nexport default function LogoutButton() {\n  const onClick = () =\u003e logout(returnTo);\n  return \u003cbutton onClick={onClick}\u003eLogout\u003c/button\u003e;\n}\n```\n\n\u003e Note: anything that is not a string will be ignored, so you can do `onClick={logout}` safely and it will ignore the event.\n\n### AuthContext\n\nJust the [React Context](https://reactjs.org/docs/context.html) that we are using in case you need access to it.\n\n## Examples\n\n### API token\n\nThe simplest way of using the token is through the hook `onUser`:\n\n```js\n// NOT RECOMMENDED\nimport Portal from \"portal\";\nimport axios from \"axios\";\n\nconst onUser = (user, token) =\u003e {\n  axios.defaults.headers.common[\"Authorization\"] = `Bearer ${token}`;\n};\n\nexport default () =\u003e \u003cPortal onUser={onUser}\u003e...\u003c/Portal\u003e;\n```\n\nHowever, this is **not recommended** because if your backend is well configured, when the token expires it will still be used and the backend will have no other option but to return an error.\n\nInstead, you should use the function `getToken()` on every request. This is normally instantaneously since it's using a locally cached copy, but on the event that it's expired:\n\n- It will attempt to refresh it on the background with an internal refreshToken\n- It will throw if even the refreshToken has expired\n\nA full implementation for your API would look normally more like this:\n\n```js\n// api.js\nimport axios from \"axios\";\nimport { getToken, logout } from \"@standard/portal\";\n\n// Create the API from an axios instance using the endpoint from the env config\nconst api = axios.create({ baseURL: process.env.REACT_APP_API });\n\n// Intercept each request to inject the token and handle logouts\napi.interceptors.request.use(\n  async config =\u003e {\n    try {\n      // Grabs the local token, or refreshes it and loads it asynchronously\n      const accessToken = await getToken();\n      config.headers = { authorization: `Bearer ${accessToken}` };\n    } catch (error) {\n      console.warn(\"Cannot find Auth0 session token; forced logout\");\n      await logout();\n      throw error;\n    }\n    return config;\n  },\n  err =\u003e err\n);\n\n// Intercept each response to deal with logouts if your API returns 401s there\napi.interceptors.response.use(\n  res =\u003e res,\n  async error =\u003e {\n    if (error.response \u0026\u0026 error.response.status === 401) {\n      const msg = \"Session in backend expired; forced logout\";\n      console.warn(msg);\n      await logout();\n    }\n    throw error;\n  }\n);\n\nexport default api;\n```\n\n### Sentry integration\n\nWe can register the user on Sentry (error reporting) taking advantage of `onUser`:\n\n```js\nimport React from \"react\";\nimport Portal from \"@standard/portal\";\nimport * as Sentry from \"@sentry/browser\";\n\n// Register the user on Sentry\nconst onUser = ({ name, email }, token) =\u003e {\n  Sentry.configureScope(function(scope) {\n    scope.setUser({ username: name, email });\n  });\n};\n\nexport default () =\u003e (\n  \u003cPortal onUser={onUser}\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n### Custom Profile\n\nWe can create any custom profile we want. Let's say we want it to be the last button in our navbar to go to a more complete full user page:\n\n```js\n// App.js\nimport Portal, { useProfile } from \"@standard/portal\";\n\nconst UserButton = () =\u003e {\n  const user = useProfile();\n  const firstname = user.name.split(\" \")[0];\n  return \u003ca href=\"/users/me\"\u003e{firstname}\u003c/a\u003e;\n};\n\nexport default () =\u003e (\n  \u003cPortal showProfile={false}\u003e\n    \u003cnav\u003e\n      {/* ... */}\n      \u003cUserButton /\u003e\n    \u003c/nav\u003e\n    \u003cdiv\u003eYour normal App code here...\u003c/div\u003e\n  \u003c/Portal\u003e\n);\n```\n\n## Thanks\n\nSpecial thanks to SVGBackgrounds.com for very cool SVG backgrounds!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstandard-ai%2Fportal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstandard-ai%2Fportal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstandard-ai%2Fportal/lists"}