{"id":18561425,"url":"https://github.com/apostrophecms/passport-bridge","last_synced_at":"2025-04-28T13:32:19.156Z","repository":{"id":50530069,"uuid":"438332008","full_name":"apostrophecms/passport-bridge","owner":"apostrophecms","description":null,"archived":false,"fork":false,"pushed_at":"2025-04-16T17:54:46.000Z","size":103,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-20T23:33:11.179Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/apostrophecms.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-14T16:55:28.000Z","updated_at":"2025-04-16T17:54:42.000Z","dependencies_parsed_at":"2025-04-02T09:38:44.475Z","dependency_job_id":null,"html_url":"https://github.com/apostrophecms/passport-bridge","commit_stats":{"total_commits":27,"total_committers":3,"mean_commits":9.0,"dds":0.2962962962962963,"last_synced_commit":"42d89d038861967cc196a6fbf70d06cd44f37398"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fpassport-bridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fpassport-bridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fpassport-bridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostrophecms%2Fpassport-bridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apostrophecms","download_url":"https://codeload.github.com/apostrophecms/passport-bridge/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251319757,"owners_count":21570451,"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":[],"created_at":"2024-11-06T22:06:48.538Z","updated_at":"2025-04-28T13:32:19.021Z","avatar_url":"https://github.com/apostrophecms.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/apostrophecms/apostrophe/main/logo.svg\" alt=\"ApostropheCMS logo\" width=\"80\" height=\"80\"\u003e\n\n  \u003ch1\u003eApostrophe Passport Bridge\u003c/h1\u003e\n  \u003cp\u003e\n    \u003ca aria-label=\"Apostrophe logo\" href=\"https://v3.docs.apostrophecms.org\"\u003e\n      \u003cimg src=\"https://img.shields.io/badge/MADE%20FOR%20ApostropheCMS-000000.svg?style=for-the-badge\u0026logo=Apostrophe\u0026labelColor=6516dd\"\u003e\n    \u003c/a\u003e\n    \u003ca aria-label=\"Test status\" href=\"https://github.com/apostrophecms/passport-bridge/actions\"\u003e\n      \u003cimg alt=\"GitHub Workflow Status (branch)\" src=\"https://img.shields.io/github/workflow/status/apostrophecms/passport-bridge/Tests/main?label=Tests\u0026labelColor=000000\u0026style=for-the-badge\"\u003e\n    \u003c/a\u003e\n    \u003ca aria-label=\"Join the community on Discord\" href=\"http://chat.apostrophecms.org\"\u003e\n      \u003cimg alt=\"\" src=\"https://img.shields.io/discord/517772094482677790?color=5865f2\u0026label=Join%20the%20Discord\u0026logo=discord\u0026logoColor=fff\u0026labelColor=000\u0026style=for-the-badge\u0026logoWidth=20\"\u003e\n    \u003c/a\u003e\n    \u003ca aria-label=\"License\" href=\"https://github.com/apostrophecms/passport-bridge/blob/main/LICENSE.md\"\u003e\n      \u003cimg alt=\"\" src=\"https://img.shields.io/static/v1?style=for-the-badge\u0026labelColor=000000\u0026label=License\u0026message=MIT\u0026color=3DA639\"\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n`apostrophe-passport` works together with `passport-google-oauth20`, `passport-gitlab2` and similar [passport](https://npmjs.org/package/passport) strategy modules to let users log in to Apostrophe CMS sites via Google, Gitlab and other identity providers. This feature is often called federation or single sign-on.\n\n## Installation\n\nTo install the module, use the command line to run this command in an Apostrophe project's root directory:\n\n```\nnpm install @apostrophecms/passport-bridge\n# Just an example — you can use many strategy modules\nnpm install --save passport-google-oauth20\n```\n\nMost modules that have \"passport\" in the name and let you log in via a third-party website will work.\n\n## Usage\n\nEnable the `@apostrophecms/passport-bridge` module in the `app.js` file:\n\n```javascript\n\nrequire('apostrophe')({\n  // Configuring baseUrl is mandatory for this module. For local dev\n  // testing you can set it to http://localhost:3000 while in production\n  // it must be real and correct\n  baseUrl: 'http://myproductionurl.com',\n  shortName: 'my-project',\n  modules: {\n    '@apostrophecms/passport-bridge': {}\n  }\n});\n```\n\nThen configure the module in `modules/@apostrophecms/passport-bridge/index.js` in your project folder:\n\n```javascript\nmodule.exports = {\n  // In modules/@apostrophecms/passport-bridge/index.js\n  options: {\n    strategies: [\n      {\n        // You must npm install --save this module in your project first\n        module: 'passport-google-oauth20',\n        options: {\n          // Options for passport-google-oauth20\n          clientID: process.env.GOOGLE_CLIENT_ID,\n          clientSecret: process.env.GOOGLE_CLIENT_SECRET\n        },\n        // Ignore users whose email address does not match this domain\n        // according to the identity provider\n        emailDomain: 'YOUR-DOMAIN-HERE.com',\n        // Use the user's email address as their identity\n        match: 'email',\n        // Strategy-specific options that must be passed to the authenticate middleware.\n        // See the documentation of the strategy module you are using\n        authenticate: {\n          // 'email' for the obvious, 'profile' for the displayName (for the create option)\n          scope: [ 'email', 'profile' ]\n        }\n      }\n    ]\n  }\n};\n```\n\n\u003e ⚠️ Since we're not using the `create` option, users must actually exist in\n\u003e Apostrophe with the same username or email address, depending on the\n\u003e `match` option. If you want to automatically create users in Apostrophe,\n\u003e see [creating users on demand](#creating-users-on-demand) below.\n\n### Customizing call to the strategy verify method\n\nAll passport strategies expect us to provide a `verify` callback. By default, `@apostrophecms/passport-bridge` passes its `findOrCreateUser` function as the `verify` callback, which works for many strategies including `passport-oauth2`, `passport-github2` and `passport-gitlab2`. For other strategies, you can pass an explicit `verify` option which will remap the strategy `verify` method with `@apostrophecms/passport-bridge` `findOrCreateUser` method.\n\nThis method is responsible for retrieving the user in the ApostropheCMS database, or creating it. It is the `@apostrophecms/passport-bridge` equivalent of the strategy `verify` method.\n\nFor example in `passport-oauth2`, the documentation shows the following\n\n```javascript\n// https://www.passportjs.org/packages/passport-oauth2/\npassport.use(new OAuth2Strategy({\n    authorizationURL: 'https://www.example.com/oauth2/authorize',\n    tokenURL: 'https://www.example.com/oauth2/token',\n    clientID: EXAMPLE_CLIENT_ID,\n    clientSecret: EXAMPLE_CLIENT_SECRET,\n    callbackURL: \"http://localhost:3000/auth/example/callback\"\n  },\n  function(accessToken, refreshToken, profile, cb) {\n    User.findOrCreate({ exampleId: profile.id }, function (err, user) {\n      return cb(err, user);\n    });\n  }\n));\n```\n\nThe second parameter of the strategy is the stratey `verify` method (`accessToken`, `refreshToken`, `profile`, `done`).\n\nThe default value for the `verify` option is equivalent to the following\n\n```javascript\nmodule.exports = {\n  // In modules/@apostrophecms/passport-bridge/index.js\n  options: {\n    strategies: [\n      {\n        module: 'passport-oauth2|passport-github2|passport-gitlab2',\n        options: {\n          // ...\n          // Default value for the verify option\n          // verify: findOrCreateUser =\u003e\n          //   async (req, accessToken, refreshToken, profile, done) =\u003e\n          //     findOrCreateUser(req, accessToken, refreshToken, profile, done)\n          }\n        },\n        // ...\n      }\n    ]\n  }\n};\n```\n\nIf you're using `passport-auth0` or any other auth strategy for which the strategy `verify` method is different, please use the new `@apostrophecms/passport-bridge` `verify` option.\n\n```javascript\n// https://www.passportjs.org/packages/passport-auth0/\nconst Auth0Strategy = require('passport-auth0');\nconst strategy = new Auth0Strategy({\n     // ...\n     state: false\n  },\n  function(accessToken, refreshToken, extraParams, profile, done) {\n    // ...\n  }\n);\n```\n\nTo solve this, you can do the following\n\n```javascript\nmodule.exports = {\n  // In modules/@apostrophecms/passport-bridge/index.js\n  options: {\n    strategies: [\n      {\n        module: 'passport-auth0',\n        options: {\n          // ...\n          verify: findOrCreateUser =\u003e\n            async (req, accessToken, refreshToken, extraParams, profile, done) =\u003e\n              findOrCreateUser(req, accessToken, refreshToken, profile, done)\n          }\n        },\n        // ...\n      }\n    ]\n  }\n};\n```\n\n`verify` is a function with a single parameter `findOrCreateUser`. `self.findOrCreateUser(spec)` will be passed to your verify function as the `findOrCreateUser` argument.\n\nYour `verify` function should return an async function which accepts `req` plus all of the `verify` parameters your particular strategy expects.\n\nThat function, in turn, return the result of `findOrCreateUser` with the appropriate parameter mapping for `accessToken`, `refreshToken`, `profile` and `done`.\n\n### Adding login links\n\nThe easiest way to enable login is to use the `loginLinks` async component in your template:\n\n```markup\n{% component \"@apostrophecms/passport-bridge:loginLinks\" %}\n```\n\nThis component will output links that attempt to bring the user back to the same page after login, and to keep them in the same locale even if your site has separate hostnames configured for separate locales.\n\nYou can override this template's markup by copying `views/loginLinks.html` from this npm module to your project-level `modules/@apostrophecms/passport-bridge/views` folder.\n\nYou can also determine the login URLs by invoking the `@apostrophecms/passport-bridge:list-urls` task, however this method does not give you a way to preserve the current URL or redirect back to the current locale's hostname.\n\n### Configuring your identity provider\n\n#### What is my oauth callback URL?\n\nMany strategies require an oauth callback URL. To discover those, run this command line task to print the URLs for login, and for the oauth callback URLs:\n\n```\nnode app @apostrophecms/passport-bridge:listUrls\n```\n\nYou'll see something like:\n\n```\nThese are the login URLs you may wish to link users to:\n\n/auth/gitlab/login\n\nThese are the callback URLs you may need to configure on sites:\n\nhttp://localhost:3000/auth/gitlab/callback\n```\n\n⚠️ You can use a URL like `http://localhost:3000` for testing but in production you must use your production URL. Most identity providers will reject a URL beginning with `http:` or an IP address, except for `http://localhost:3000` which is often accepted for testing purposes only.\n\n#### Where do I get my `clientID`, `clientSecret`, etc.?\n\nYou get these from the identity provider, usually by adding an \"app\" to your profile or developer console. In the case of Google you will need to [create an application in the Google API console and authorize it to perform oauth logins](https://developers.google.com/). See the documentation of the passport strategy module you're using.\n\n### Creating users on demand\n\nIf you wish you can enable automatic creation of new accounts for any user who is valid according to your login strategy, for instance any user in your Google workspace.\n\n```javascript\nmodule.exports = {\n  // In modules/@apostrophecms/passport-bridge/index.js\n  options: {\n    ...\n    create: {\n      // If you wish to treat all valid google users in your domain as\n      // admins of the site. See also `guest`, `contributor`, `editor`\n      //\n      role: 'admin'\n    }\n  }\n};\n```\n\n### Beefing up the \"create\" option: copying extra properties\n\nThe \"create\" option shown above will create a user with minimal information: first name, last name, full name, username, and email address (where available).\n\nIf you wish to import other fields from the profile object provided by the passport strategy, add an `import` function to your configuration for that strategy. The `import` function receives `(profile, user)` and may copy properties from `profile` to `user` as it sees fit. It may not be an async function.\n\n### Multiple strategies\n\nYou may enable more than one strategy at the same time. Just configure them consecutively in the `strategies` array. This means you can have login via Twitter, Google, etc. on the same site.\n\n\u003e ⚠️ Take care when choosing what identity providers to trust. When using single sign-on, your site's security is only as good as that of the identity provider you are trusting. If multiple strategies are enabled with `email` as the matching method, and a malicious user succeeds in creating an account with that email address that matches any of the strategies, then that is sufficient for them to log in. Most major public providers, like Facebook, Twitter or Google, do require the user to prove they control an email address before associating it with an account.\n\n## Accessing the user's `accessToken` and `refreshToken` to make API calls\n\nWhen we authenticate the user via an identity provider like `github` that has APIs\nof its own, it is often desirable to call additional APIs of that provider.\n\nSetting the `retainAccessToken` option to `true` retains the `accessToken` and `refreshToken` in Apostrophe's\n\"safe,\" which is a special storage place for sensitive data associated with a user.\n\nYou can then access that data like this:\n\n```javascript\nconst tokens = await self.apos.user.getTokens(req.user, 'github');\nif (tokens) {\n  // Use tokens.accessToken and, sometimes, tokens.refreshToken\n} else {\n  // Tell the user to connect with github again\n}\n```\n\nA passport strategy name is always required. Unfortunately, this is not the same thing as\nthe npm module name. If you do not know the strategy name, check\nthe `strategy.js` file in the source code of the Passport strategy module you are\nusing, such as `passport-github`.\n\nThere is no guarantee that a particular strategy supports tokens, or requires both\n`accessToken` and `refreshToken`.\n\nAccess tokens can expire. If the access token expires and the strategy you are using\nsupports OAuth refresh tokens, you can ask Apostrophe to refresh it:\n\n```javascript\n// Passing in the existing refresh token is optional, but avoids an extra database call\nconst { accessToken, refreshToken } = await self.apos.user.refreshTokens(req.user, 'github', refreshToken);\n```\n\nIf the refresh fails, an exception is thrown. In addition, if it fails with a\n\"401: Unauthorized\" error, the tokens are removed, so that the next call\nto `getTokens` will return null.\n\nIf you need to refresh the tokens yourself by other means, you can pass in the result:\n\n```javascript\n// We obtained these new tokens by means of our own\nawait self.apos.user.updateTokens(req.user, 'github', { accessToken, refreshToken });\n```\n\nPassing in the existing access token and refresh token is optional, and avoids\nwaiting for an extra database call.\n\n\u003e Determining whether an access token has expired will depend on the platform-specific APIs you\nare calling, but most will return a `401` status code in this situation.\n\nTo simplify this flow, use `withAccessToken`. Here is an example\nwhere the github Octokit API is used. The API request in the nested function is first made with\nthe existing access token. If an exception with a `status` property equal to `401`\nis thrown, the token is refreshed and updated, and the nested function is invoked again\nwith the new token. If the refreshed access token also fails with a `401`, the error is\nallowed to throw. All other errors are allowed to pass through.\n\n```javascript\nconst { Octokit } = require(\"@octokit/rest\");\n\nconst repos = await self.apos.user.withAccessToken(req.user, 'github', async (accessToken, unauthorized) =\u003e {\n  const octokit = Octokit({ auth: accessToken });\n  return req.octokit.rest.repos.listForAuthenticatedUser({\n    affiliation: 'owner',\n    // 100 is the max allowed per page\n    per_page: 100\n  });\n});\n// Do something cool with `repos`\n```\n\nNot all APIs that expect access tokens are created equal. If the API you are calling throws\nan error in this situation that doesn't have `status: 401`, you can throw a suitable\nobject yourself (pseudocode):\n\n```javascript\ntry {\n  await someStrangeAPI(accessToken);\n} catch (e) {\n  // Just an example, your mileage will vary\n  if (e.toString().includes('unauthorized')) {\n    throw {\n      status: 401\n    };\n  } else {\n    // Some other error, let it fail\n    throw e;\n  }\n}\n```\n\n## Issues with multiple services\n\n### Conflicting usernames\n\nIf a user is already logged in, for instance via Apostrophe's standard login screen,\nand then passes through the Passport flow to log in via a second identity provider,\nPassport will log the user out of the first account by default, and in most cases\nwill wind up creating a second account, or mistakenly reuse an account associated\nwith a different service.\n\nThis problem can be mitigated by setting `match` to `email` for each strategy, as long\nas the user has the same email address in each case and the service in question\noffers email addresses as an option.\n\n### \"Connecting\" accounts without creating a second account\n\nAn individual may want to associate an ordinary Apostrophe account with a secondary service,\nsuch as a github account, that has a different email address. Unfortunately, in this case,\nsimply following a link to the login URL for a second service this will log the user out of\nthe first account and log them into an entirely separate account based on the email address\nfrom github when using `match: 'email'` as described above. If using `match: 'id'`, the\nbehavior is more consistent, but still undesirable: a separate account is always created.\n\nThis can be addressed via the following flow:\n\n1. The user logs in normally to their Apostrophe account.\n\n2. Await `requestConnection` to generate a confirmation link and email it\nto the current user's email address. When this method resolves, the email has been\nhanded off for delivery, and it is appropriate to tell the user to expect it soon.\n\n\u003e Apostrophe must be\n\u003e [correctly configured for reliable email delivery](https://v3.docs.apostrophecms.org/guide/sending-email.html#sending-email-from-your-apostrophe-project).\n\u003e If you do not take appropriate steps to ensure this, the email probably will not get through.\n\n```javascript\nawait self.apos.user.requestConnection(req, 'STRATEGY NAME HERE', {\n  redirectTo: '/site/relative/url/here',\n});\n```\n\n\u003e The strategy name depends on the passport strategy in question. `passport-github` uses\n\u003e the strategy name `github`. You can find it in the source of the strategy module\n\u003e you are using and it is usually your first guess as well.\n\n3. The user receives the email and follows the link provided.\n\n4. The user is redirected to authorize access to their `github` account (in this example).\n\n5. The user is redirectd to the home page, or to the URL you optionally specify via\n`redirectTo`. They are still logged into the original account. Their strategy-specific id\nis captured in their `user` piece as `githubId` (in the case of the github strategy;\nsubstitute the appropriate strategy name), and their tokens are available as described\nearlier if `retainSessionToken: true` is set.\n\n\u003e Note that for security reasons, the link in the email is only valid for twenty-four hours.\n\n### Overriding the email template\n\nTo override the email message that is sent, copy `views/connectEmail.html` from\nthe `@apostrophecms/passport-bridge` npm module to your project-level\n`modules/@apostrophecms/passport-bridge/views` folder, and edit that template you see fit.\n\n### Session properties\n\nNote that when following this flow the user's original req.session properties are\npreserved. Normally this is not possible, because Passport 0.6 or better always\nregenerates the session on a new login.\n\n### Logging in via the secondary strategy\n\nIn this example, a user who \"connects\" their account to github will be able to\n\"log in via github\" in the future, if they so choose. Since we trust that github\nmaintains good security, and they proved control of the original account before\nconnecting with github, this is usually acceptable.\n\nHowever, if you wish to block this for a particular strategy you can specify\nthe `login: false` option when configuring that strategy. If you take this\npath, users will be able to \"connect\" an account using that strategy to their\noriginal account, but will not be able to log in via that strategy alone. In this\nsituation the secondary strategy is present for API token access only.\n\n### Disconnecting a strategy from an account\n\nYou can disconnect a strategy at any time:\n\n```javascript\nawait self.apos.user.removeConnection(req, 'STRATEGY NAME HERE');\n```\n\nThis will clear the related strategy-specific id, e.g. it will purge `githubId`\nif the strategy name is `github`.\n\n## Frequently asked questions\n\n### Where do I `require` the passport strategy?\n\nYou don't. Apostrophe does it for you. You pass its configuration as part of the `strategies` option, via the `options` sub-property and sometimes also the `authenticate` sub-property if your chosen strategy  has options that must be passed to its `authenticate` middleware, as with Google (you'll see this in its documentation).\n\n### Can I change how users are mapped between the identity provider and my site?\n\nIf you don't like the default behavior, you can change it. The mapping is up to you. Usernames and emails are *almost* permanent, but people do change them and that can be problematic, especially if they are reused by someone else.\n\nOn the other hand, IDs are a pain to work with if you are creating users in advance and not using the `create` feature of the module.\n\nYou can set the `match` option for any strategy to one of the following choices:\n\n#### `id`\n\nMatches on the id of their profile as returned by the strategy module. This is most unique, however if you don't set `create`, then you'll need to find out the ids of users in advance and populate them in your database. You could do that by adding a string field to the `fields` configuration of the `@apostrophecms/user` module in your project.\n\nTo accommodate multiple strategies, If the strategy name is `google`, then the id needs to be in the `googleId` field of the user. If the strategy name is `gitlab`, the id needs to be in `gitlabId`, and so on. If you are using the `create` feature, these properties are automatically populated for you.\n\n**The strategy name and the npm module name are not quite the same thing.** Look at the output of `node app @apostrophecms/passport-bridge:list-urls`. The word that follows `/auth` is the strategy name.\n\n#### `email`\n\nThis will match on any email the authentication provider indicates they own, whether it is an array in the `.emails` property of their profile containing objects with `.value` properties (as with Google), an array of strings in `.emails`, or just an `email` string property. *To minimize confusion you can also set `match` to `emails` which has the same effect. Either way it will check all three cases.*\n\n#### `username`\n\nThe default. Users are matched based on having the same username.\n\n#### A function of your choice\n\nIf you provide a function rather than a string, it will receive the user's profile from the passport strategy, and must return a MongoDB criteria object matching the appropriate user. Do not worry about checking the `disabled` or `type` properties, Apostrophe will handle that.\n\n### How can I reject users in a customized way?\n\nYou can set your own policy for rejecting users by passing an `accept` function for any strategy. This function takes the `profile` object provided by the passport strategy and must return `true` otherwise the user is not permitted to log in.\n\n### How can I lock down my site by email address domain name?\n\nYou may wish to accept only users from one email domain, which is very handy if your company's email is hosted by Google (aka \"G Suite\", aka \"Google Workspaces\"). For that, also set the `emailDomain` option to the domain name you wish to allow. All others are rejected. This is very important if you are using the `create` option.\n\n### How can I reject direct logins via Apostrophe's login form?\n\n\"This is great, but I want to disable the regular `/login` page.\" You can:\n\n```javascript\n// in app.js\nmodules: {\n  '@apostrophecms/passport-bridge': {\n    // As above; this is not where we disable local login...\n  },\n  '@apostrophecms/login': {\n    // We disable it here, by configuring the built-in @apostrophecms/login module\n    localLogin: false\n  }\n}\n```\n\nThe built-in login page is powered by Passport's `local` strategy, which is added to Apostrophe by the standard `@apostrophecms/login` module. That's why we disable it there and not in `@apostrophecms/passport-bridge`'s options.\n\n### How can I override the error page?\n\nIf login fails, for instance because you are matching on `email` but the `username` duplicates another account, or because a user is valid in Google but `emailDomain` does not match, the `error.html` template of the `apostrophe-passport` module is rendered. By default, it works, but it's pretty ugly! You'll want to customize it to your project's needs.\n\nLike other templates in Apostrophe, you can override this template by copying it to `modules/@apostrophecms/passport-bridge/views/error.html` *in your project* (**never modify the npm module itself**). You can then extend your own layout template and so on, just as you have most likely already done for the 404 Not Found page.\n\n### How can I redirect the standard `/login` page to one of my strategies?\n\nOnce you have disabled the regular login page, it's possible for you to decide what happens at that URL. Use the [@apostrophecms/redirect](https://npmjs.org/package/@apostrophecms/redirect) module to set it up through a nice UI, or add an Express route and a redirect in your own code.\n\n### What if it doesn't work?\n\nFeel free to open an issue but be sure to provide full specifics and a test project. Note that some strategies may not follow the standard practices this module is built upon. Those written by Jared Hanson, the author of Passport, or following his best practices should work well. You might want to test directly with the sample code provided with that strategy module first, to rule out problems with the module or with your configuration of it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostrophecms%2Fpassport-bridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapostrophecms%2Fpassport-bridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostrophecms%2Fpassport-bridge/lists"}