Passport.js
Blitz provides an adapter that lets you use an existing
Passport.js authentication strategy.
Currently only passport strategies that use a
verify
callback are supported. In the Twitter example below, the second argument to TwitterStrategy()
is the verify
callback.
Setup
1. Add the Passport.js API Route
Add a new api route at
app/api/auth/[...auth].ts
with the following contents.
// app/api/auth/[...auth].tsimport {passportAuth} from "blitz"import db from "db"export default passportAuth({ successRedirectUrl: "/", errorRedirectUrl: "/", strategies: [ { strategy: new PassportStrategy(), // Provide initialized passport strategy here }, ],})
If you need, you can place the api route at a different path but the filename must be
[...auth].js
or [...auth].ts
.
URLs
The
passportAuth
adapter adds two API endpoints for each installed strategy.
With the handler at
app/api/auth/[...auth].ts
, it adds the following:
/api/auth/[strategyName]
- URL to initiate login/api/auth/[strategyName]/callback
- Callback URL to complete login
For example with
passport-twitter
strategy, the URLs for Twitter will be:
/api/auth/twitter
- URL to initiate login/api/auth/twitter/callback
- Callback URL to complete login
You can determine the
strategyName
in the strategy’s documentation by looking for this: passport.authenticate('github')
. So in this case, the strategyName
is github
.
SSL Proxy Configuration
You may need to set
secureProxy
option to true
in case your app is located behind SSL proxy (Nginx). Proxy should be set to manage x-forwarded-proto
header correctly.
// app/api/auth/[...auth].tsimport {passportAuth} from "blitz"import db from "db"export default passportAuth({ successRedirectUrl: "/", errorRedirectUrl: "/", secureProxy: true, strategies: [ /*...*/ ],})
2. Add a Passport Strategy
Add a strategy to the
strategies
array argument for passportAuth
in the API route, and then follow the strategy’s documentation for setup.
Here’s an example of adding
passport-twitter
.
Note that the
callbackURL
uses the callback endpoint as described above (/api/auth/twitter/callback
)
import {passportAuth} from "blitz"import db from "db"import {Strategy as TwitterStrategy} from "passport-twitter"export default passportAuth({ successRedirectUrl: "/", errorRedirectUrl: "/", strategies: [ { strategy: new TwitterStrategy( { consumerKey: process.env.TWITTER_CONSUMER_KEY, consumerSecret: process.env.TWITTER_CONSUMER_SECRET, callbackURL: process.env.NODE_ENV === "production" ? "https://example.com/api/auth/twitter/callback" : "http://localhost:3000/api/auth/twitter/callback", includeEmail: true, }, async function (_token, _tokenSecret, profile, done) { const email = profile.emails && profile.emails[0]?.value if (!email) { // This can happen if you haven't enabled email access in your twitter app permissions return done(new Error("Twitter OAuth response doesn't have email.")) } const user = await db.user.upsert({ where: {email}, create: { email, name: profile.displayName, }, update: {email}, }) const publicData = {userId: user.id, roles: [user.role], source: "twitter"} done(null, {publicData}) }, ), }, ],})
Note: The above
passport-twitter
example requires your User
prisma model to have email String @unique
and name String
.
3. Log in with this Passport Strategy
Add a link to your app with URL format of
/api/auth/[strategyName]
.
For the above twitter example, the link would be like this:
<a href="/api/auth/twitter">Log In With Twitter</a>
Detailed Usage Instructions
Upon successful authentication with the third-party, the user will be redirected back to the above auth API route. When that happens, the
verify
callback will be called.
When the
verify
callback is called, the user has been authenticated with the third-party, but a session has not yet been created for your Blitz app.
Create a Session
To create a new Blitz session, you need to call the
done()
function from your verify
callback.
done(null, result)
where
result
is an object of type VerifyCallbackResult
export type VerifyCallbackResult = { publicData: PublicData privateData?: Record<string, any> redirectUrl?: string}
The Blitz adapter will then call
session.create()
for you and redirect the user back to the correct place in your application.
Return an Error
If instead, you want to prevent creating a session because of some error, then call
done()
with an error as the first argument. The user will then be redirected back to the correct location.
return done(new Error("it broke"))
Showing the Error to the User
Any error during this process will be provided as the
authError
query parameter.
For example with
errorRedirectUrl = '/'
and done(new Error("it broke"))
, the user will be redirected to:
/?authError=it broke
Post Authentication Redirects
There are four different ways to determine the redirect URL where a user should be sent after they are authenticated. They are listed here in order of priority. A URL provided with method #1 will override all other URLs.
- Add
redirectUrl
to theverify
callback result- Example:
done(null, {publicData, redirectUrl: '/'})
- Example:
- Add a
redirectUrl
query parameter to the “initiate login” url- Example:
example.com/api/auth/twitter?redirectUrl=/dashboard
- Example:
example.com/api/auth/twitter?redirectUrl=${router.pathname}
- Example:
- Via the config passed to
passportAuth
- If success, it will use
config.successRedirectUrl
- If error, it will use
config.errorRedirectUrl
- If success, it will use
- If none of the above are provided, it will redirect to
/
Note: If there is an error, methods #1 and #2 will override
config.errorRedirectUrl
This should give you maximum flexibility to do anything you need. If this doesn’t meet your needs, please open an issue on GitHub!
authenticateOptions
Some strategies have to call an option like
scope
or successMessage
inside the passport.authenticate()
method. Add these options to the passportAuth
object like this:
import {passportAuth} from "blitz"import db from "db"import {Strategy as Auth0Strategy} from "passport-auth0"export default passportAuth({ successRedirectUrl: "/", errorRedirectUrl: "/", strategies: [ { authenticateOptions: {scope: "openid email profile"}, strategy: new Auth0Strategy( { domain: process.env.AUTH0_DOMAIN, clientID: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, callbackURL: process.env.NODE_ENV === "production" ? "https://example.com/api/auth/auth0/callback" : "http://localhost:3000/api/auth/auth0/callback", }, async function (_token, _tokenSecret, extraParams, profile, done) { const email = profile.emails && profile.emails[0]?.value if (!email) { // This can happen if you haven't enabled email access in your twitter app permissions return done(new Error("GitHub OAuth response doesn't have email.")) } const user = await db.user.upsert({ where: {email}, create: { email, name: profile.displayName, }, update: {email}, }) const publicData = { userId: user.id, roles: [user.role], source: "auth0", } done(undefined, {publicData}) }, ), }, ],})
Note: Without the
authenticateOptions
the profile
parameter inside the verify
function would not contain any values.