Microsoft provider does not return usable accessToken #1174

Closed
opened 2026-03-13 08:26:31 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @zpg6 on GitHub (May 7, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Login with Microsoft for an account where you have a profile picture. The fetch of the profile photo will silently fail (401). I encountered this trying to use the accessToken to authenticate with other Microsoft APIs I had provided scopes for, and it was giving the same error. I traced it back to failing on the profile picture as well, which should reflect my same issue.

Current vs. Expected behavior

Here is how it works currently:

// 1. We should be checking options.disableProfilePhoto before the fetch

await betterFetch<ArrayBuffer>(
	`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
	{
		headers: {
			Authorization: `Bearer ${token.accessToken}`,
		},
		async onResponse(context) {
			if (options.disableProfilePhoto || !context.response.ok) {
                                // 2. These requests are always failing
				console.log(context);
				return;
			}
			try {
				const response = context.response.clone();
				const pictureBuffer = await response.arrayBuffer();
				const pictureBase64 = base64.encode(pictureBuffer);
				user.picture = `data:image/jpeg;base64, ${pictureBase64}`;
			} catch (e) {
				console.log("Error getting profile photo", e);
				logger.error(
					e && typeof e === "object" && "name" in e
						? (e.name as string)
						: "",
					e,
				);
			}
		},
	},
);

Previously there was not a console.log in the if (options.disableProfilePhoto || !context.response.ok) { so that caused the silent failure.

When I console.log(context) in the onResponse it gives:

{
    "response": {},
    "request": {
        "headers": {},
        "url": "https://graph.microsoft.com/v1.0/me/photos/48x48/%24value",
        "body": null,
        "method": "GET",
        "signal": {}
    }
}

If I take the link https://graph.microsoft.com/v1.0/me and use my account.accessToken stored in the database I get this error:

Status: 401
{
	"error": {
		"code": "InvalidAuthenticationToken",
		"message": "IDX14100: JWT is not well formed, there are no dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EncodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'."
	}
}

I would instead expect 200.

What version of Better Auth are you using?

v1.2.8-beta.4

Provide environment information

- OS: MacOS
- Browser: Chrome

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
    database: drizzleAdapter(process.env.DATABASE, {
        provider: "sqlite",
        usePlural: true,
        debugLogs: true,
    }),
    session: {
        storeSessionInDatabase: true,
    },
    socialProviders: {
        microsoft: {
            clientId: process.env.MICROSOFT_CLIENT_ID,
            clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
        },
    },
    secret: process.env.BETTER_AUTH_SECRET,
    baseURL: SITE_CONFIG.baseUrl,
    basePath: "/api/auth",
});

Additional context

I noticed NextAuth offers OIDC for their Microsoft login:
https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/microsoft-entra-id.ts

Originally created by @zpg6 on GitHub (May 7, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Login with Microsoft for an account where you have a profile picture. The fetch of the profile photo will silently fail (401). I encountered this trying to use the accessToken to authenticate with other Microsoft APIs I had provided scopes for, and it was giving the same error. I traced it back to failing on the profile picture as well, which should reflect my same issue. ### Current vs. Expected behavior Here is how it works currently: ``` // 1. We should be checking options.disableProfilePhoto before the fetch await betterFetch<ArrayBuffer>( `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, { headers: { Authorization: `Bearer ${token.accessToken}`, }, async onResponse(context) { if (options.disableProfilePhoto || !context.response.ok) { // 2. These requests are always failing console.log(context); return; } try { const response = context.response.clone(); const pictureBuffer = await response.arrayBuffer(); const pictureBase64 = base64.encode(pictureBuffer); user.picture = `data:image/jpeg;base64, ${pictureBase64}`; } catch (e) { console.log("Error getting profile photo", e); logger.error( e && typeof e === "object" && "name" in e ? (e.name as string) : "", e, ); } }, }, ); ``` Previously there was not a console.log in the `if (options.disableProfilePhoto || !context.response.ok) {` so that caused the silent failure. When I `console.log(context)` in the onResponse it gives: ``` { "response": {}, "request": { "headers": {}, "url": "https://graph.microsoft.com/v1.0/me/photos/48x48/%24value", "body": null, "method": "GET", "signal": {} } } ``` If I take the link `https://graph.microsoft.com/v1.0/me` and use my `account.accessToken` stored in the database I get this error: ``` Status: 401 { "error": { "code": "InvalidAuthenticationToken", "message": "IDX14100: JWT is not well formed, there are no dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EncodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'." } } ``` I would instead expect 200. ### What version of Better Auth are you using? v1.2.8-beta.4 ### Provide environment information ```bash - OS: MacOS - Browser: Chrome ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ database: drizzleAdapter(process.env.DATABASE, { provider: "sqlite", usePlural: true, debugLogs: true, }), session: { storeSessionInDatabase: true, }, socialProviders: { microsoft: { clientId: process.env.MICROSOFT_CLIENT_ID, clientSecret: process.env.MICROSOFT_CLIENT_SECRET, }, }, secret: process.env.BETTER_AUTH_SECRET, baseURL: SITE_CONFIG.baseUrl, basePath: "/api/auth", }); ``` ### Additional context I noticed NextAuth offers OIDC for their Microsoft login: https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/microsoft-entra-id.ts
Author
Owner

@moshetanzer commented on GitHub (Jul 30, 2025):

Did you ever manage to solve this?

@moshetanzer commented on GitHub (Jul 30, 2025): Did you ever manage to solve this?
Author
Owner

@zpg6 commented on GitHub (Aug 4, 2025):

Did you ever manage to solve this?

@moshetanzer Yes, in fact I made a plug-in so this is easier for others in the future. better-auth-microsoft-graph makes it easier to connect BetterAuth to Outlook, Onedrive, and other Microsoft APIs.

https://github.com/zpg6/better-auth-microsoft-graph

Contributions welcome

@zpg6 commented on GitHub (Aug 4, 2025): > Did you ever manage to solve this? @moshetanzer Yes, in fact I made a plug-in so this is easier for others in the future. `better-auth-microsoft-graph` makes it easier to connect BetterAuth to Outlook, Onedrive, and other Microsoft APIs. https://github.com/zpg6/better-auth-microsoft-graph Contributions welcome
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1174