@@ -6,7 +6,7 @@ import { hasSSOAccess } from '@/lib/billing'
66import { env } from '@/lib/core/config/env'
77import { REDACTED_MARKER } from '@/lib/core/security/redaction'
88
9- const logger = createLogger ( 'SSO-Register ' )
9+ const logger = createLogger ( 'SSORegisterRoute ' )
1010
1111const mappingSchema = z
1212 . object ( {
@@ -43,6 +43,11 @@ const ssoRegistrationSchema = z.discriminatedUnion('providerType', [
4343 ] )
4444 . default ( [ 'openid' , 'profile' , 'email' ] ) ,
4545 pkce : z . boolean ( ) . default ( true ) ,
46+ // Optional explicit endpoints - if not provided, fetched from OIDC discovery
47+ authorizationEndpoint : z . string ( ) . url ( ) . optional ( ) ,
48+ tokenEndpoint : z . string ( ) . url ( ) . optional ( ) ,
49+ userInfoEndpoint : z . string ( ) . url ( ) . optional ( ) ,
50+ jwksEndpoint : z . string ( ) . url ( ) . optional ( ) ,
4651 } ) ,
4752 z . object ( {
4853 providerType : z . literal ( 'saml' ) ,
@@ -64,12 +69,10 @@ const ssoRegistrationSchema = z.discriminatedUnion('providerType', [
6469
6570export async function POST ( request : NextRequest ) {
6671 try {
67- // SSO plugin must be enabled in Better Auth
6872 if ( ! env . SSO_ENABLED ) {
6973 return NextResponse . json ( { error : 'SSO is not enabled' } , { status : 400 } )
7074 }
7175
72- // Check plan access (enterprise) or env var override
7376 const session = await getSession ( )
7477 if ( ! session ?. user ?. id ) {
7578 return NextResponse . json ( { error : 'Authentication required' } , { status : 401 } )
@@ -116,7 +119,16 @@ export async function POST(request: NextRequest) {
116119 }
117120
118121 if ( providerType === 'oidc' ) {
119- const { clientId, clientSecret, scopes, pkce } = body
122+ const {
123+ clientId,
124+ clientSecret,
125+ scopes,
126+ pkce,
127+ authorizationEndpoint,
128+ tokenEndpoint,
129+ userInfoEndpoint,
130+ jwksEndpoint,
131+ } = body
120132
121133 const oidcConfig : any = {
122134 clientId,
@@ -127,48 +139,90 @@ export async function POST(request: NextRequest) {
127139 pkce : pkce ?? true ,
128140 }
129141
130- // Add manual endpoints for providers that might need them
131- // Common patterns for OIDC providers that don't support discovery properly
132- if (
133- issuer . includes ( 'okta.com' ) ||
134- issuer . includes ( 'auth0.com' ) ||
135- issuer . includes ( 'identityserver' )
136- ) {
137- const baseUrl = issuer . includes ( '/oauth2/default' )
138- ? issuer . replace ( '/oauth2/default' , '' )
139- : issuer . replace ( '/oauth' , '' ) . replace ( '/v2.0' , '' ) . replace ( '/oauth2' , '' )
140-
141- // Okta-style endpoints
142- if ( issuer . includes ( 'okta.com' ) ) {
143- oidcConfig . authorizationEndpoint = `${ baseUrl } /oauth2/default/v1/authorize`
144- oidcConfig . tokenEndpoint = `${ baseUrl } /oauth2/default/v1/token`
145- oidcConfig . userInfoEndpoint = `${ baseUrl } /oauth2/default/v1/userinfo`
146- oidcConfig . jwksEndpoint = `${ baseUrl } /oauth2/default/v1/keys`
147- }
148- // Auth0-style endpoints
149- else if ( issuer . includes ( 'auth0.com' ) ) {
150- oidcConfig . authorizationEndpoint = `${ baseUrl } /authorize`
151- oidcConfig . tokenEndpoint = `${ baseUrl } /oauth/token`
152- oidcConfig . userInfoEndpoint = `${ baseUrl } /userinfo`
153- oidcConfig . jwksEndpoint = `${ baseUrl } /.well-known/jwks.json`
154- }
155- // Generic OIDC endpoints (IdentityServer, etc.)
156- else {
157- oidcConfig . authorizationEndpoint = `${ baseUrl } /connect/authorize`
158- oidcConfig . tokenEndpoint = `${ baseUrl } /connect/token`
159- oidcConfig . userInfoEndpoint = `${ baseUrl } /connect/userinfo`
160- oidcConfig . jwksEndpoint = `${ baseUrl } /.well-known/jwks`
161- }
142+ const hasExplicitEndpoints = authorizationEndpoint && tokenEndpoint && jwksEndpoint
162143
163- logger . info ( 'Using manual OIDC endpoints for provider' , {
144+ if ( hasExplicitEndpoints ) {
145+ oidcConfig . authorizationEndpoint = authorizationEndpoint
146+ oidcConfig . tokenEndpoint = tokenEndpoint
147+ oidcConfig . userInfoEndpoint = userInfoEndpoint
148+ oidcConfig . jwksEndpoint = jwksEndpoint
149+
150+ logger . info ( 'Using explicitly provided OIDC endpoints' , {
164151 providerId,
165- provider : issuer . includes ( 'okta.com' )
166- ? 'Okta'
167- : issuer . includes ( 'auth0.com' )
168- ? 'Auth0'
169- : 'Generic' ,
170- authEndpoint : oidcConfig . authorizationEndpoint ,
152+ issuer,
153+ authorizationEndpoint : oidcConfig . authorizationEndpoint ,
154+ tokenEndpoint : oidcConfig . tokenEndpoint ,
155+ userInfoEndpoint : oidcConfig . userInfoEndpoint ,
156+ jwksEndpoint : oidcConfig . jwksEndpoint ,
171157 } )
158+ } else {
159+ const discoveryUrl = `${ issuer . replace ( / \/ $ / , '' ) } /.well-known/openid-configuration`
160+ try {
161+ logger . info ( 'Fetching OIDC discovery document' , { discoveryUrl } )
162+
163+ const discoveryResponse = await fetch ( discoveryUrl , {
164+ headers : { Accept : 'application/json' } ,
165+ } )
166+
167+ if ( ! discoveryResponse . ok ) {
168+ logger . error ( 'Failed to fetch OIDC discovery document' , {
169+ status : discoveryResponse . status ,
170+ statusText : discoveryResponse . statusText ,
171+ } )
172+ return NextResponse . json (
173+ {
174+ error : `Failed to fetch OIDC discovery document from ${ discoveryUrl } . Status: ${ discoveryResponse . status } ` ,
175+ } ,
176+ { status : 400 }
177+ )
178+ }
179+
180+ const discovery = await discoveryResponse . json ( )
181+
182+ if (
183+ ! discovery . authorization_endpoint ||
184+ ! discovery . token_endpoint ||
185+ ! discovery . jwks_uri
186+ ) {
187+ logger . error ( 'OIDC discovery document missing required endpoints' , {
188+ hasAuthEndpoint : ! ! discovery . authorization_endpoint ,
189+ hasTokenEndpoint : ! ! discovery . token_endpoint ,
190+ hasJwksUri : ! ! discovery . jwks_uri ,
191+ } )
192+ return NextResponse . json (
193+ {
194+ error :
195+ 'OIDC discovery document is missing required endpoints (authorization_endpoint, token_endpoint, jwks_uri)' ,
196+ } ,
197+ { status : 400 }
198+ )
199+ }
200+
201+ oidcConfig . authorizationEndpoint = discovery . authorization_endpoint
202+ oidcConfig . tokenEndpoint = discovery . token_endpoint
203+ oidcConfig . userInfoEndpoint = discovery . userinfo_endpoint
204+ oidcConfig . jwksEndpoint = discovery . jwks_uri
205+
206+ logger . info ( 'Successfully fetched OIDC endpoints from discovery' , {
207+ providerId,
208+ issuer,
209+ authorizationEndpoint : oidcConfig . authorizationEndpoint ,
210+ tokenEndpoint : oidcConfig . tokenEndpoint ,
211+ userInfoEndpoint : oidcConfig . userInfoEndpoint ,
212+ jwksEndpoint : oidcConfig . jwksEndpoint ,
213+ } )
214+ } catch ( error ) {
215+ logger . error ( 'Error fetching OIDC discovery document' , {
216+ error : error instanceof Error ? error . message : 'Unknown error' ,
217+ discoveryUrl,
218+ } )
219+ return NextResponse . json (
220+ {
221+ error : `Failed to fetch OIDC discovery document from ${ discoveryUrl } . Please verify the issuer URL is correct.` ,
222+ } ,
223+ { status : 400 }
224+ )
225+ }
172226 }
173227
174228 providerConfig . oidcConfig = oidcConfig
0 commit comments