BA
Better Auth•2mo ago
Jim-Y

OIDC Provider questions

Hi everyone! Trying to do a POC on the oidc provider feature. I have a next.js application cosplaying the auth server. Relevant code excerpts:
export const auth = betterAuth({
emailAndPassword: {enabled: true},
database: new Pool({connectionString: process.env.DATABASE_URL}),
plugins: [nextCookies(), jwt(), oidcProvider({
loginPage: '/auth/sign-in',
consentPage: '/oidc/consent',
defaultScope: 'openid email',
allowDynamicClientRegistration: true
})]
});
export const auth = betterAuth({
emailAndPassword: {enabled: true},
database: new Pool({connectionString: process.env.DATABASE_URL}),
plugins: [nextCookies(), jwt(), oidcProvider({
loginPage: '/auth/sign-in',
consentPage: '/oidc/consent',
defaultScope: 'openid email',
allowDynamicClientRegistration: true
})]
});
I also have an API route /api/oidc/callback. Previously I registered an OIDC client with
await oauth2.register({
name: clientName,
redirectURLs: ['http://localhost:3000/api/oidc/callback']
});
await oauth2.register({
name: clientName,
redirectURLs: ['http://localhost:3000/api/oidc/callback']
});
So I start the oidc flow by pasting the following url to the browser
http://localhost:3000/api/auth/oauth2/authorize?response_type=code&client_id=<redacted>&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Foidc%2Fcallback
http://localhost:3000/api/auth/oauth2/authorize?response_type=code&client_id=<redacted>&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Foidc%2Fcallback
I get redirected, correctly, to the sign-in page
http://localhost:3000/auth/sign-in?response_type=code&client_id=<redacted>&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Foidc%2Fcallback
http://localhost:3000/auth/sign-in?response_type=code&client_id=<redacted>&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Foidc%2Fcallback
But on the login page after authClient.signIn.email() I don't get redirected but I get HTTP200 with the user payload. -Continue in a following comment-
7 Replies
Jim-Y
Jim-YOP•2mo ago
So I tried two ways to login: Client side login With authClient.signIn.email() where authClient is
export const authClient = createAuthClient({
plugins: [oidcClient()]
})
export const authClient = createAuthClient({
plugins: [oidcClient()]
})
const signin = async () => {
const res = await signIn.email({
email,
password
});
console.log(res);
}
const signin = async () => {
const res = await signIn.email({
email,
password
});
console.log(res);
}
Server-side login With auth.api.signInEmail My problem is that neither method redirects me to neither the consent nor the callbackURI. Both methods just return HTTP 200 and the payload:
{
"redirect": false,
"token": "<some_token>",
"user": {
"id": "UAJmyiqzWtlU2uiNsITNW6XTI7fAnizs",
"email": "<email>",
"name": "<name>",
"image": null,
"emailVerified": false,
"createdAt": "2025-03-07T12:52:08.535Z",
"updatedAt": "2025-03-07T12:52:08.535Z"
}
}
{
"redirect": false,
"token": "<some_token>",
"user": {
"id": "UAJmyiqzWtlU2uiNsITNW6XTI7fAnizs",
"email": "<email>",
"name": "<name>",
"image": null,
"emailVerified": false,
"createdAt": "2025-03-07T12:52:08.535Z",
"updatedAt": "2025-03-07T12:52:08.535Z"
}
}
If I first login, then I hit the authorize endpoint, then skipping the login screen the callback uri gets called with the code which I could exchange for an access_token on the token endpoint with postman. Additional question If I have a payload like
{
"access_token": "owsjcxomiiwqjyfqlrnwjyybspzbhgwe",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email",
"id_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJVQUpteWlxeld0bFUydWlOc0lUTlc2WFRJN2ZBbml6cyIsImF1ZCI6ImFua2hjeXZubXprY3VqaGh3b3phcWluaHBkdG91eGxkIiwiaWF0IjoxNzQxMzY1ODg2LCJhY3IiOiJ1cm46bWFjZTppbmNvbW1vbjppYXA6c2lsdmVyIiwiZW1haWwiOiJhdHRpbGEua2xpbmdAcGVyc29uYXIuYWkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImV4cCI6MTc0MTM2OTQ4Nn0.qGDxiDnnLqZOmfveiZgH86OHjKHzOlFIYopF3vkEayo"
}
{
"access_token": "owsjcxomiiwqjyfqlrnwjyybspzbhgwe",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email",
"id_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJVQUpteWlxeld0bFUydWlOc0lUTlc2WFRJN2ZBbml6cyIsImF1ZCI6ImFua2hjeXZubXprY3VqaGh3b3phcWluaHBkdG91eGxkIiwiaWF0IjoxNzQxMzY1ODg2LCJhY3IiOiJ1cm46bWFjZTppbmNvbW1vbjppYXA6c2lsdmVyIiwiZW1haWwiOiJhdHRpbGEua2xpbmdAcGVyc29uYXIuYWkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImV4cCI6MTc0MTM2OTQ4Nn0.qGDxiDnnLqZOmfveiZgH86OHjKHzOlFIYopF3vkEayo"
}
How can the resource server validate the access_token at the provider? As I saw there is no token_introspection endpoint implemented?! Thanks folks 🙂
Deolin
Deolin•2mo ago
i had same problem i had to add some of my own code same for if user has 2fa and using using the oidc
bekacru
bekacru•2mo ago
is this only breaking when the user isn't signed in? yeah introspection endpoint isn't implemented but for id_token you can validate locally using your secret key since it's signed with your secret. And JWKs implementation will be added soon. if a user has 2fa, it's not handled properly. Happy to take a look if you can send a PR with your implementation 👀
Jim-Y
Jim-YOP•2mo ago
Hi! It seems, this happens when the user is not yet signed in, the authorize endpoint redirects to the login screen, the login screen calls (in my case) signIn.email. The expected would be to get a 302 to the consent screen or to the callback url, but instead the signIn.email call get http 200 and the token and user object, so the oauth flow just breaks. The documentation says that you don't have to change your signin logic and that the plugin will handle the redirect(s) and I was wondering if that's the case. That's good for an oidc use-case but I am trying to use better-auth as a basis for an authn/authz server issuing access_tokens. So it would be a 3-legged system. Frontend, API backend and authz server with better auth. In this scenario the API backend gets an access_token from the frontend (the frontend previously got it from the authz server) and so the API needs to validate the access_token (and not the id_token). As I saw, the access_token is an opaque token right now and not a jwt, so it can't use the JWKS uri to validate the token, for an opaque token it would need an introspection endpoint OR arguably even better, the access_token could be a JWT so the API could validate it without a roundtrip to the authz server (better-auth).
bekacru
bekacru•2mo ago
Yeah it should. The rediction relies on cookies. Check if cookies are being set after you're redirected to the sign in page. The cookie should have oidc somewhere in its name. If you're using the OIDC plugin as an authz layer to issue API tokens, you might want to check out the API Keys plugin. The OIDC provider is primarily meant for 3d-party apps to authenticate with your app.
Jim-Y
Jim-YOP•2mo ago
Will check it thanks, also I will try to reproduce it on an isolated example and let you know about it if reproducible.
Deolin
Deolin•2mo ago
I've added this functionality to my Next.js code, and I'll add you to it when I have time,maybe you can get some ideas from it for the next update. I implemented a couple of cookies. For example, if a user is redirected to the sign-in page during oidc, the URL data is stored in a cookie so we can use it later either after 2FA, after even after sign-up or sign in with no 2fa. I haven't done sign up logic but Since email verification is required, I would probably also make it so that after verifying their email, the user is redirected using the stored cookie data. On the front end, I implemented the sign-in page to include a default redirect when no OIDC or redirect URL is provided. Like domain.com/sign-in?redirect-url=google.com

Did you find this page helpful?