title | sidebar_label |
---|---|
Passport.js |
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.
Add a new api route at app/api/auth/[...auth].ts
with the following contents.
// app/api/auth/[...auth].ts
import {passportAuth} from "blitz"
import db from "db"
export default passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
strategies: [
// 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
.
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
.
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].ts
import {passportAuth} from "blitz"
import db from "db"
export default passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
secureProxy: true, // highlight-line
strategies: [/*...*/],
})
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: [
// highlight-start
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})
},
),
// highlight-end
],
})
Note: The above passport-twitter
example requires your User
prisma model to have email String @unique
and name String
.
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.
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.
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"))
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
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!
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: "/",
// highlight-start
authenticateOptions: {scope: "openid email profile"},
// highlight-end
strategies: [
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.