mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-07 20:37:44 +00:00
fix: OAuth flow errors (#189)
* fix: remove codeverifier * chore: release v0.4.10-beta.6 * fix: add claims and proper scopes for twitch * chore: release v0.4.10-beta.6 * chore: release v0.4.10-beta.7 * fix: generic oauth should use the code from qparam * chore: release v0.4.10-beta.8 * fix: scope failing to merge * chore: release v0.4.10-beta.9 * fix: twitch get profile * chore: release v0.4.10-beta.10 * fix:lint
This commit is contained in:
@@ -15,7 +15,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { signIn } from "@/lib/auth-client";
|
||||
import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||
import { Key, Loader2 } from "lucide-react";
|
||||
import { Key, Loader2, TwitchIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
@@ -129,6 +129,28 @@ export default function SignIn() {
|
||||
>
|
||||
<DiscordLogoIcon />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full gap-2"
|
||||
onClick={async () => {
|
||||
await signIn.social({
|
||||
provider: "twitch",
|
||||
callbackURL: "/dashboard",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.64 5.93h1.43v4.28h-1.43m3.93-4.28H17v4.28h-1.43M7 2L3.43 5.57v12.86h4.28V22l3.58-3.57h2.85L20.57 12V2m-1.43 9.29l-2.85 2.85h-2.86l-2.5 2.5v-2.5H7.71V3.43h11.43Z"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full gap-2"
|
||||
|
||||
@@ -107,5 +107,9 @@ export const auth = betterAuth({
|
||||
clientId: process.env.MICROSOFT_CLIENT_ID || "",
|
||||
clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "",
|
||||
},
|
||||
twitch: {
|
||||
clientId: process.env.TWITCH_CLIENT_ID || "",
|
||||
clientSecret: process.env.TWITCH_CLIENT_SECRET || "",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ export const forgetPassword = createAuthEndpoint(
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.emailAndPassword?.sendResetPassword) {
|
||||
ctx.context.logger.error(
|
||||
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!",
|
||||
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function in your auth config!",
|
||||
);
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: "Reset password isn't enabled",
|
||||
|
||||
@@ -39,7 +39,7 @@ export const signInOAuth = createAuthEndpoint(
|
||||
);
|
||||
if (!provider) {
|
||||
c.context.logger.error(
|
||||
"Provider not found. Make sure to add the provider to your auth config",
|
||||
"Provider not found. Make sure to add the provider in your auth config",
|
||||
{
|
||||
provider: c.body.provider,
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ export async function createAuthorizationURL({
|
||||
state,
|
||||
codeVerifier,
|
||||
scopes,
|
||||
claims,
|
||||
disablePkce,
|
||||
redirectURI,
|
||||
}: {
|
||||
@@ -19,6 +20,7 @@ export async function createAuthorizationURL({
|
||||
codeVerifier?: string;
|
||||
scopes: string[];
|
||||
disablePkce?: boolean;
|
||||
claims?: string[];
|
||||
}) {
|
||||
const url = new URL(authorizationEndpoint);
|
||||
url.searchParams.set("response_type", "code");
|
||||
@@ -32,6 +34,20 @@ export async function createAuthorizationURL({
|
||||
url.searchParams.set("code_challenge_method", "S256");
|
||||
url.searchParams.set("code_challenge", codeChallenge);
|
||||
}
|
||||
|
||||
if (claims) {
|
||||
const claimsObj = claims.reduce(
|
||||
(acc, claim) => {
|
||||
acc[claim] = null;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, null>,
|
||||
);
|
||||
url.searchParams.set(
|
||||
"claims",
|
||||
JSON.stringify({
|
||||
id_token: { email: null, email_verified: null, ...claimsObj },
|
||||
}),
|
||||
);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
|
||||
accessTokenExpiresAt: data.expires_at
|
||||
? new Date((Date.now() + data.expires_in) * 1000)
|
||||
: undefined,
|
||||
scopes: data.scope?.split(" ") || [],
|
||||
scopes: data?.scope
|
||||
? typeof data.scope === "string"
|
||||
? data.scope.split(" ")
|
||||
: data.scope
|
||||
: [],
|
||||
idToken: data.id_token,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -301,8 +301,9 @@ export const genericOAuth = (options: GenericOAuthOptions) => {
|
||||
}
|
||||
const state = ctx.query.state;
|
||||
const {
|
||||
data: { callbackURL, currentURL, code },
|
||||
data: { callbackURL, currentURL },
|
||||
} = parsedState;
|
||||
const code = ctx.query.code;
|
||||
const errorURL =
|
||||
parsedState.data?.currentURL || `${ctx.context.baseURL}/error`;
|
||||
const storedState = await ctx.getSignedCookie(
|
||||
|
||||
@@ -26,11 +26,10 @@ export const facebook = (options: FacebookOptions) => {
|
||||
return await createAuthorizationURL({
|
||||
id: "facebook",
|
||||
options,
|
||||
authorizationEndpoint: "https://www.facebook.com/v16.0/dialog/oauth",
|
||||
authorizationEndpoint: "https://www.facebook.com/v21.0/dialog/oauth",
|
||||
scopes: _scopes,
|
||||
state,
|
||||
redirectURI,
|
||||
codeVerifier,
|
||||
});
|
||||
},
|
||||
validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
|
||||
@@ -39,12 +38,12 @@ export const facebook = (options: FacebookOptions) => {
|
||||
codeVerifier,
|
||||
redirectURI: options.redirectURI || redirectURI,
|
||||
options,
|
||||
tokenEndpoint: "https://graph.facebook.com/v16.0/oauth/access_token",
|
||||
tokenEndpoint: "https://graph.facebook.com/oauth/access_token",
|
||||
});
|
||||
},
|
||||
async getUserInfo(token) {
|
||||
const { data: profile, error } = await betterFetch<FacebookProfile>(
|
||||
"https://graph.facebook.com/me",
|
||||
"https://graph.facebook.com/me?fields=id,name,email,picture",
|
||||
{
|
||||
auth: {
|
||||
type: "Bearer",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { betterFetch } from "@better-fetch/fetch";
|
||||
import type { OAuthProvider, ProviderOptions } from "../oauth2";
|
||||
import { logger } from "../utils";
|
||||
import { parseJWT } from "oslo/jwt";
|
||||
import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
|
||||
|
||||
export interface TwitchProfile {
|
||||
@@ -21,13 +23,15 @@ export interface TwitchProfile {
|
||||
picture: string;
|
||||
}
|
||||
|
||||
export interface TwitchOptions extends ProviderOptions {}
|
||||
export interface TwitchOptions extends ProviderOptions {
|
||||
claims?: string[];
|
||||
}
|
||||
export const twitch = (options: TwitchOptions) => {
|
||||
return {
|
||||
id: "twitch",
|
||||
name: "Twitch",
|
||||
createAuthorizationURL({ state, scopes, redirectURI }) {
|
||||
const _scopes = options.scope || scopes || ["activity:write", "read"];
|
||||
const _scopes = options.scope || scopes || ["user:read:email", "openid"];
|
||||
return createAuthorizationURL({
|
||||
id: "twitch",
|
||||
redirectURI,
|
||||
@@ -35,6 +39,11 @@ export const twitch = (options: TwitchOptions) => {
|
||||
authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
|
||||
scopes: _scopes,
|
||||
state,
|
||||
claims: options.claims || [
|
||||
"email",
|
||||
"email_verified",
|
||||
"preferred_username",
|
||||
],
|
||||
});
|
||||
},
|
||||
validateAuthorizationCode: async ({ code, redirectURI }) => {
|
||||
@@ -46,18 +55,12 @@ export const twitch = (options: TwitchOptions) => {
|
||||
});
|
||||
},
|
||||
async getUserInfo(token) {
|
||||
const { data: profile, error } = await betterFetch<TwitchProfile>(
|
||||
"https://api.twitch.tv/helix/users",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (error) {
|
||||
const idToken = token.idToken;
|
||||
if (!idToken) {
|
||||
logger.error("No idToken found in token");
|
||||
return null;
|
||||
}
|
||||
const profile = parseJWT(idToken)?.payload as TwitchProfile;
|
||||
return {
|
||||
user: {
|
||||
id: profile.sub,
|
||||
|
||||
Reference in New Issue
Block a user