BA
Better Authā€¢7d ago
nibir

Need Help Single Sign-On (SSO) with OIDC Provider Plugin

Hey everyone! I'm integrating Single Sign-On (SSO) with Better Auth using the OIDC Provider Plugin across two Next.js apps:
Main app (Backend + Auth) ā†’ http://localhost:3000
Frontend (SSO Login UI) ā†’ http://localhost:3001
šŸ”¹ Current Setup Main App - Auth Configuration (auth.ts)
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import {
oidcProvider,
phoneNumber,
openAPI,
admin,
organization,
jwt,
} from "better-auth/plugins";
import { sso } from "better-auth/plugins/sso";
import { authSchema } from "@/db/schema";
import { nextCookies } from "better-auth/next-js";


export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql", "sqlite"
schema: {
...authSchema,
user: authSchema.user,
},
}),

trustedOrigins: ["http://localhost:3001"],

account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "test-app"],
},
},

user: {
additionalFields: {
jobTitle: {
type: "string",
required: false,
},
},
},
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},

plugins: [
phoneNumber(),
openAPI(),
organization(),
admin(),
nextCookies(),
oidcProvider({
loginPage: "/sign-in",
consentPage: "/oauth2/authorize",
requirePKCE: true,
}),
jwt(),
sso(),
]
});
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import {
oidcProvider,
phoneNumber,
openAPI,
admin,
organization,
jwt,
} from "better-auth/plugins";
import { sso } from "better-auth/plugins/sso";
import { authSchema } from "@/db/schema";
import { nextCookies } from "better-auth/next-js";


export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql", "sqlite"
schema: {
...authSchema,
user: authSchema.user,
},
}),

trustedOrigins: ["http://localhost:3001"],

account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "test-app"],
},
},

user: {
additionalFields: {
jobTitle: {
type: "string",
required: false,
},
},
},
emailAndPassword: {
enabled: true,
},
socialProviders: {
google: {
clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
},
},

plugins: [
phoneNumber(),
openAPI(),
organization(),
admin(),
nextCookies(),
oidcProvider({
loginPage: "/sign-in",
consentPage: "/oauth2/authorize",
requirePKCE: true,
}),
jwt(),
sso(),
]
});
No description
No description
3 Replies
nibir
nibirOPā€¢7d ago
Main App - Client Configuration (auth-client.ts)
import { createAuthClient } from "better-auth/react";
import {
inferAdditionalFields,
phoneNumberClient,
organizationClient,
adminClient,
oidcClient,
jwtClient,
ssoClient,
} from "better-auth/client/plugins";
import { toast } from "sonner";

export const client = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL, // the base url of your auth server
plugins: [
organizationClient(),
ssoClient(),
adminClient(),
jwtClient(),
oidcClient(),
phoneNumberClient(),
inferAdditionalFields({
user: {
jobTitle: {
type: "string",
required: false,
},
},
}),
],
fetchOptions: {
onError(e) {
if (e.error.status === 429) {
toast.error("Too many requests. Please try again later.");
}
},
},
});

export const {
signUp,
signIn,
signOut,
useSession,
deleteUser,
admin
} = client;
import { createAuthClient } from "better-auth/react";
import {
inferAdditionalFields,
phoneNumberClient,
organizationClient,
adminClient,
oidcClient,
jwtClient,
ssoClient,
} from "better-auth/client/plugins";
import { toast } from "sonner";

export const client = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL, // the base url of your auth server
plugins: [
organizationClient(),
ssoClient(),
adminClient(),
jwtClient(),
oidcClient(),
phoneNumberClient(),
inferAdditionalFields({
user: {
jobTitle: {
type: "string",
required: false,
},
},
}),
],
fetchOptions: {
onError(e) {
if (e.error.status === 429) {
toast.error("Too many requests. Please try again later.");
}
},
},
});

export const {
signUp,
signIn,
signOut,
useSession,
deleteUser,
admin
} = client;
Frontend - SSO Login (page.tsx) - 3001
"use client";

import { Button } from "@/components/ui/button";
import { createAuthClient } from "better-auth/react"
import { ssoClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [ ssoClient()],
})

export default function SsoLogin() {
const handleClick = async () => {
const res = await authClient.signIn.sso(
{
providerId: "test-app",
callbackURL: "/dashboard",
}
);
console.log(res);
};

const handleRegister = async () => {
const res = await authClient.sso.register(
{
issuer: "http://localhost:3000/api/auth",
domain: "localhost.com",
providerId: "test-app",
clientId: "ksrbewcpwkbasyxjtfivxbacwmlgyjjz",
clientSecret: "icsyzwjtcdzgfhbjupdmifehklglmbko",
authorizationEndpoint:
"http://localhost:3000/api/auth/oauth2/authorize",
tokenEndpoint: "http://localhost:3000/api/auth/oauth2/token",
jwksEndpoint: "http://localhost:3000/api/auth/jwks",
pkce: true,
}
);
console.log(res);
};
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Button onClick={handleClick}>Sso Login</Button>
<Button onClick={handleRegister}>Register</Button>
</main>
</div>
);
}
"use client";

import { Button } from "@/components/ui/button";
import { createAuthClient } from "better-auth/react"
import { ssoClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
plugins: [ ssoClient()],
})

export default function SsoLogin() {
const handleClick = async () => {
const res = await authClient.signIn.sso(
{
providerId: "test-app",
callbackURL: "/dashboard",
}
);
console.log(res);
};

const handleRegister = async () => {
const res = await authClient.sso.register(
{
issuer: "http://localhost:3000/api/auth",
domain: "localhost.com",
providerId: "test-app",
clientId: "ksrbewcpwkbasyxjtfivxbacwmlgyjjz",
clientSecret: "icsyzwjtcdzgfhbjupdmifehklglmbko",
authorizationEndpoint:
"http://localhost:3000/api/auth/oauth2/authorize",
tokenEndpoint: "http://localhost:3000/api/auth/oauth2/token",
jwksEndpoint: "http://localhost:3000/api/auth/jwks",
pkce: true,
}
);
console.log(res);
};
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Button onClick={handleClick}>Sso Login</Button>
<Button onClick={handleRegister}>Register</Button>
</main>
</div>
);
}
āŒ Issue 1: /oauth2/token Returns 401 Logs:

POST /api/auth/oauth2/token 401
GET /api/auth/error/error?error=invalid_provider&error_description=token_response_not_found

POST /api/auth/oauth2/token 401
GET /api/auth/error/error?error=invalid_provider&error_description=token_response_not_found

Reason:
- Missing client_id and client_secret in /oauth2/token request
āœ… Fix:
- Manually added client_id and client_secret via hooks
hooks: {
before: createAuthMiddleware(async (ctx) => {
if (ctx.path === "/oauth2/token") {
return {
context: {
...ctx,
body: {
...ctx.body,
client_id: "ksrbewcpwkbasyxjtfivxbacwmlgyjjz",
client_secret: "icsyzwjtcdzgfhbjupdmifehklglmbko",
},
},
};
}
}),
},
hooks: {
before: createAuthMiddleware(async (ctx) => {
if (ctx.path === "/oauth2/token") {
return {
context: {
...ctx,
body: {
...ctx.body,
client_id: "ksrbewcpwkbasyxjtfivxbacwmlgyjjz",
client_secret: "icsyzwjtcdzgfhbjupdmifehklglmbko",
},
},
};
}
}),
},
- Now /oauth2/token returns 200 OK āœ…
āŒ Issue 2: "Key not found" & "token_not_verified" Logs:

POST /api/auth/oauth2/token 200
GET /api/auth/jwks 200
[Better Auth]: Error: Key not found
GET /api/auth/error/error?error=invalid_provider&error_description=token_not_verified

POST /api/auth/oauth2/token 200
GET /api/auth/jwks 200
[Better Auth]: Error: Key not found
GET /api/auth/error/error?error=invalid_provider&error_description=token_not_verified

Reason:
- The issued token cannot be verified with the JWKS keys
JWKS Response (/api/auth/jwks):
{
"keys": [
{
"crv": "Ed25519",
"x": "d_aGBZ-mriny68ulckvvsaCHLi1Go64nNzjKCmR0vpY",
"kty": "OKP",
"kid": "eqfft7yeL6QdVeWitEFY834lMYXDyWpr"
}
]
}
{
"keys": [
{
"crv": "Ed25519",
"x": "d_aGBZ-mriny68ulckvvsaCHLi1Go64nNzjKCmR0vpY",
"kty": "OKP",
"kid": "eqfft7yeL6QdVeWitEFY834lMYXDyWpr"
}
]
}

If anyone has faced similar issues or knows a better fix, please share your insights! Thanks in advance! šŸ˜Š
Kwae
Kwaeā€¢7d ago
are you doing PKCE?
nibir
nibirOPā€¢6d ago
Yes, but failid to verifcation.
No description

Did you find this page helpful?