Integrating Enterprise SSO with NextAuth
If you’re already using NextAuth for authentication, but now need to support SAML or Enterprise-level Single Sign-On (SSO), this guide is for you.
PropelAuth Configuration
Since you're already integrated with NextAuth, we're assuming you already have a login page. In this guide, we’ll be extending NextAuth to allow enterprise customers to sign in with their IdP via your login page.
We’ll start with making some changes in the PropelAuth dashboard.
- Update Login Methods - Let’s navigate to the Signup / Login page. Here we can enable SAML but, since you already have other login methods enabled through NextAuth, we can disable every other login method. While we’re here, let’s disable the Allow public signups setting by navigating to the Settings tab.
-
Organization Settings - Head over to the Organization Settings page and disable the Users can create their own orgs setting.
-
(Optional) Setup User Properties - If you have user properties such as name, username, and profile picture that you would like to collect from the IdP, head over to the User Properties page to enable them. You can also create a Custom User Property if you don’t see what you’re looking for.
-
(Optional) Enable Roles - If you already have orgs, you likely have roles as well. If you setup that same role structure with us, then your users can specify role information in the SAML connection and will show up with that role information after they login. If you’re wanting to use roles, navigate to the Roles & Permissions page and edit or create roles to match your own.
Creating Orgs for your Users
Since SAML is setup on an org to org basis, we need to make sure each user belongs to an org. This can be done manually through the PropelAuth dashboard (via the Organizations page) or programmatically through the PropelAuth API.
When creating an org, it’s essential that we also set the org’s domain (for example, “acme.com”).
We can then enable SAML for the org. We can do this either in the org’s Settings page or through the API.
Allowing your users to set up their SAML connection
Since SAML is the only enabled login method, we need a way for employees of an organization to set up their SAML connection. To do this, navigate to the Organizations page in your PropelAuth dashboard, find the org, click Settings, and click on the Generate Link button.
This button will generate a link to allow an employee of an organization to set up SAML. This link does not require authentication to access and is intended to be used by an employee of the org, such as a member of their IT team.
NextAuth Setup
Let's now create a provider in our NextAuth app that we'll use to log users in with SAML. Start by heading over to the Frontend Integration page, clicking on Advanced Settings followed by Edit OAuth Config. Here, we can generate a Client ID and Client Secret. We'll be using these in the next step.
While we're here, let's also add a Redirect URI. The URI should be your Application's URL + /api/auth/callback/PropelAuthProvider
. For example:
http://localhost:3000/api/auth/callback/PropelAuthProvider
Moving over to your NextAuth project, create a .env.local
file in your root directory and add the following, replacing the values with your own. You can find YOUR_AUTH_URL
in the Frontend Integrations page in your PropelAuth dashboard:
PROPELAUTH_PROVIDER_CLIENT_ID={YOUR_CLIENT_ID}
PROPELAUTH_PROVIDER_CLIENT_SECRET={YOUR_CLIENT_SECRET}
PROPELAUTH_AUTH_URL={YOUR_AUTH_URL}
Next, create a new file in your project called PropelAuthProvider.js
where we'll build a custom OAuth provider.
export default function PropelAuthProvider() {
return {
id: 'PropelAuthProvider',
name: 'PropelAuthProvider',
idToken: true,
type: 'oauth',
version: '2.0',
issuer: process.env.PROPELAUTH_AUTH_URL,
jwks_endpoint: `${process.env.PROPELAUTH_AUTH_URL}/.well-known/jwks.json`,
authorization: `${process.env.PROPELAUTH_AUTH_URL}/propelauth/oauth/authorize`,
clientId: process.env.PROPELAUTH_PROVIDER_CLIENT_ID,
clientSecret: process.env.PROPELAUTH_PROVIDER_CLIENT_SECRET,
token: `${process.env.PROPELAUTH_AUTH_URL}/propelauth/oauth/token`,
userinfo: `${process.env.PROPELAUTH_AUTH_URL}/propelauth/oauth/userinfo`,
profile(data) {
return {
id: data.user_id,
email: data.email,
}
}
}
}
If you haven't already done so, create a file called [...nextauth].js
in your pages/api/auth
directory. Here we will add the PropelAuthProvider
we created in the last step as a provider, as well as add some callbacks to handle user information. This is where you can customize the user data that will be available to you via the useSession() hook, such as org_id
and role
.
import NextAuth from "next-auth";
// Update the location of the PropelAuthProvider
import PropelAuthProvider from "../../providers/PropelAuthProvider";
export default NextAuth({
providers: [
PropelAuthProvider(),
],
callbacks: {
async jwt(token) {
if (token.profile) {
// here you can add properties from the token.profile object
console.log(token.profile)
// {
// sub: '0497bbe6-49c1-4bc7-9e9c-c75846722c73',
// aud: '376fd41d3109415c0063bbcac3fbe490',
// iat: 1712281392,
// exp: 1712283192,
// user_id: '0497bbe6-49c1-4bc7-9e9c-c75846722c73',
// iss: 'https://4309742291.propelauthtest.com',
// email: 'test@propelauth.com',
// first_name: 'Anthony',
// last_name: 'Edwards',
// org_member_info: {
// org_id: '35905720-f22a-4f36-b082-7f35bb32463f',
// org_name: 'PropelAuth',
// url_safe_org_name: 'propelauth',
// org_metadata: {},
// user_role: 'Owner',
// inherited_user_roles_plus_current_role: [ 'Owner', 'Admin', 'Member' ],
// user_permissions: [
// 'propelauth::can_invite',
// 'propelauth::can_change_roles',
// 'propelauth::can_remove_users',
// 'propelauth::can_setup_saml',
// 'propelauth::can_manage_api_keys'
// ]
// }
// }
token.token.org_id = token.profile.org_member_info.org_id
token.token.org_name = token.profile.org_member_info.org_name
token.token.role = token.profile.org_member_info.user_role
}
return token.token;
},
async session(session) {
return session
}
}
});
Creating a Sign in Page
Now that we have everything set up in PropelAuth as well as a provider in NextAuth, let’s add a "Login With Enterprise SSO" button to our sign in page.
Enterprise SSO is not as simple as a “Sign in with Google” (or Social SSO) option. You can’t just place a button that says “Sign in with Okta” because you need to know which Okta account they are signing in to.
Some common examples include:
- Ask for the user’s email address up front, see if an SSO connection exists for it, and if so, begin the login process. Otherwise, ask for their password.
- Adding a “Sign in with SSO” button - when the user presses it, we prompt them for their email address.
- Adding a “Sign in with SSO” button - when the user presses it, we prompt them for their organization’s name.
PropelAuth provides options that support all of these and more, but let’s look at the second example where we collect the user’s email address. We can check if an email address has a SAML connection by fetching the following endpoint:
{YOUR_AUTH_URL}/api/fe/v3/login/check?email={YOUR_USERS_EMAIL}
If the user has a SAML connection, the request will return a SAML login URL. If not, it will return a 404. Let’s create a button that checks if the user has a SAML connection:
function SignUpPage() {
const [email, setEmail] = useState('')
const [message, setMessage] = useState(null);
const loginWithEnterpriseSSO = async () => {
const response = await fetch(`{YOUR_AUTH_URL}/api/fe/v3/login/check?email=${email}`);
if (response.status === 200) {
// TODO: handle login
setMessage("The user belongs to an org")
} else if (response.status === 404) {
setMessage("User does not belong to an org")
}
};
return (
<div>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button onClick={() => LoginWithEnterpriseSSO()}>
Login With Enterprise SSO
</button>
{message && <div>{message}</div>}
</div>
);
}
export default SignUpPage
When we test this out with a user who belongs to an org with a SAML connection, we should see this as a result:
Let’s now update the loginWithEnterpriseSSO
function to handle the NextAuth login and call it when our SAML check succeeds. Here’s some additional documentation on the signIn
function that we’ll be using. Instead of using the email
property, you can also use domain
, org_id
, or org_name
.
const loginWithEnterpriseSSO = async () => {
const response = await fetch(`{YOUR_AUTH_URL}/api/fe/v3/login/check?email=${email}`);
if (response.status === 200) {
// instead of email you can use domain, org_id, or org_name
signIn("PropelAuthProvider", null, { email: email })
} else if (response.status === 404) {
setMessage("User does not belong to an org")
}
};
We can now update the page to check if a user is signed in. For this example, we'll be using the useSession
hook.
"use client"
import { useSession } from "next-auth/react";
import React, { useState } from 'react';
function SignUpPage() {
const session = useSession();
if (session.status === 'loading') {
return "Loading or not authenticated..."
} else if (session.status === "unauthenticated") {
return (
// SSO Enterprise Login Button
);
} else {
return (
<div>
{session && <h2>Welcome, {session.data.token.email}</h2>}
</div>
);
}
}
export default SignUpPage
And that’s it! Your users can now:
- Setup SAML
- Login via SAML
- Show up with org details, roles, and user properties when they login
You’re now all set and ready to managing your users with Enterprise SSO!