Dan6erbond / sk-auth

Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!
MIT License
581 stars 70 forks source link

Is Twitter provider working? #53

Open saneef opened 3 years ago

saneef commented 3 years ago

Tried with Twitter, and it doesn't seems like working?

It gets stuck at a Twitter Error page at https://api.twitter.com/oauth/authorize?oauth_token=undefined.

saneef commented 3 years ago

I hadn't enabled Enable 3-legged OAuth on Twitter 🤦‍♂️

Sorry.

saneef commented 3 years ago

Even after enabling 3-legged OAuth on Twitter, I'm facing this issue? Any clues?

angezanetti commented 2 years ago

I have the same issue - with OAuth v1 and v2.

Did you managed to make it work?

mitjakukovec commented 2 years ago

I just can't find where I can select Enable 3-legged OAuth. I am also not absolutely sure if I need to configure Twitter provider with API secret and key or with OAUTH client id and secret?

        new TwitterAuthProvider({
            apiKey: import.meta.env.VITE_TWITTER_API_KEY,
            apiSecret: import.meta.env.VITE_TWITTER_API_SECRET,
            profile(profile) {
                return { ...profile, provider: 'twitter' };
            },
        })

or

        new TwitterAuthProvider({
            apiKey: import.meta.env.VITE_TWITTER_OAUTH_CLIENT_ID,
            apiSecret: import.meta.env.VITE_TWITTER_OAUTH_CLIENT_SECRET,
            profile(profile) {
                return { ...profile, provider: 'twitter' };
            },
        })

There is API key/secret, Access token/secret and Oauth client id/secret. image So which one and how to use?

nk113 commented 2 years ago

Would be better to write own twitter provider... I could have got this to work using twitter-api-v2 like below:

export const auth: SvelteKitAuth = new SvelteKitAuth({
  ...
  providers: [
    ...
    new class extends TwitterAuthProvider {
      constructor() {
        super({
          apiKey: process.env.AUTH_TWITTER_API_KEY,
          apiSecret: process.env.AUTH_TWITTER_API_SECRET,
          profile: (profile) => {
            return { ...profile, provider: 'twitter' };
          },
        })
      }
      getRequestToken = async (auth, host): Promise<any> => {
        const { url, ...oauthResult } = await (new TwitterApi({
          appKey: this.config.apiKey,
          appSecret: this.config.apiSecret
        })).readOnly.generateAuthLink(
          encodeURIComponent(this.getCallbackUri()), { authAccessType: 'read' }
        );
        return {
          oauthToken: oauthResult.oauth_token,
          oauthTokenSecret: oauthResult.oauth_token_secret,
          oauthCallbackConfirmed: oauthResult.oauth_callback_confirmed
        };
      }
      getTokens = async (oauthToken: string, oauthVerifier: string): Promise<any> => {
        const endpoint = 'https://api.twitter.com/oauth/access_token';

        const data = {
          oauth_consumer_key: this.config.apiKey,
          oauth_token: oauthToken,
          oauth_verifier: oauthVerifier,
        };

        const response: any = await fetch(`${endpoint}?${new URLSearchParams(data)}`, { method: 'POST' });
        // This endpoint returns query string like key-value pairs
        // https://developer.twitter.com/en/docs/authentication/api-reference/access_token
        return Object.fromEntries([...(new URLSearchParams(await response.text()))]);
      }
      getUserProfile = async ({ oauth_token, oauth_token_secret, ...account }: any) => {
        let user: any = {};
        try {
          // Need to apply for elevated access - not tested yet
          user = await (new TwitterApi({
            appKey: this.config.apiKey,
            appSecret: this.config.apiSecret,
            accessToken: oauth_token,
            accessSecret: oauth_token_secret,
          })).readOnly.currentUser();
        } catch (e) {
          // 403
        }
        return { ...user, ...account };
      }
      getCallbackUri(): string {
        return `${settings.AUTH_CALLBACK_URI_PREFIX}twitter`;
      }
    },
    ...
xpluscal commented 2 years ago

Would be better to write own twitter provider... I could have got this to work using twitter-api-v2 like below:

export const auth: SvelteKitAuth = new SvelteKitAuth({
  ...
  providers: [
    ...
    new class extends TwitterAuthProvider {
      constructor() {
        super({
          apiKey: process.env.AUTH_TWITTER_API_KEY,
          apiSecret: process.env.AUTH_TWITTER_API_SECRET,
          profile: (profile) => {
            return { ...profile, provider: 'twitter' };
          },
        })
      }
      getRequestToken = async (auth, host): Promise<any> => {
        const { url, ...oauthResult } = await (new TwitterApi({
          appKey: this.config.apiKey,
          appSecret: this.config.apiSecret
        })).readOnly.generateAuthLink(
          encodeURIComponent(this.getCallbackUri()), { authAccessType: 'read' }
        );
        return {
          oauthToken: oauthResult.oauth_token,
          oauthTokenSecret: oauthResult.oauth_token_secret,
          oauthCallbackConfirmed: oauthResult.oauth_callback_confirmed
        };
      }
      getTokens = async (oauthToken: string, oauthVerifier: string): Promise<any> => {
        const endpoint = 'https://api.twitter.com/oauth/access_token';

        const data = {
          oauth_consumer_key: this.config.apiKey,
          oauth_token: oauthToken,
          oauth_verifier: oauthVerifier,
        };

        const response: any = await fetch(`${endpoint}?${new URLSearchParams(data)}`, { method: 'POST' });
        // This endpoint returns query string like key-value pairs
        // https://developer.twitter.com/en/docs/authentication/api-reference/access_token
        return Object.fromEntries([...(new URLSearchParams(await response.text()))]);
      }
      getUserProfile = async ({ oauth_token, oauth_token_secret, ...account }: any) => {
        let user: any = {};
        try {
          // Need to apply for elevated access - not tested yet
          user = await (new TwitterApi({
            appKey: this.config.apiKey,
            appSecret: this.config.apiSecret,
            accessToken: oauth_token,
            accessSecret: oauth_token_secret,
          })).readOnly.currentUser();
        } catch (e) {
          // 403
        }
        return { ...user, ...account };
      }
      getCallbackUri(): string {
        return `${settings.AUTH_CALLBACK_URI_PREFIX}twitter`;
      }
    },
    ...

Hey! When you mean using your own here. There's a reference to "new TwitterApi" in there. Where are you getting this from?

nk113 commented 2 years ago

O sorry the link above doesn't work... I employed this twitter-api-v2 library https://github.com/PLhery/node-twitter-api-v2 which is referred in https://developer.twitter.com/en/docs/twitter-api/tools-and-libraries/v2

caiges commented 2 years ago

I did something like this to support OAuth 1.0a with Twitter:

import { TwitterAuthProvider } from "sk-auth/providers";
import { TwitterApi } from 'twitter-api-v2';

const twitterProfileHandler = ({ id, id_str, name, screen_name, description, profile_image_url, profile_image_url_https, verified }) => ({
  id, id_str, name, screen_name, description, profile_image_url, profile_image_url_https, verified
});

const defaultConfig = {
  id: "twitter",
  profile: twitterProfileHandler,
}

export class TwitterV2AuthProvider extends TwitterAuthProvider {
  constructor(config) {
    super({
      ...defaultConfig,
      ...config
    })
  }
  async getRequestToken(auth, host, state) {
    const { url, ...oauthResult } = await (new TwitterApi({
      appKey: this.config.apiKey,
      appSecret: this.config.apiSecret
    })).readOnly.generateAuthLink(
      encodeURIComponent(this.getCallbackUri(auth, host, state)), { authAccessType: 'read' }
    );
    return {
      oauthToken: oauthResult.oauth_token,
      oauthTokenSecret: oauthResult.oauth_token_secret,
      oauthCallbackConfirmed: oauthResult.oauth_callback_confirmed
    };
  }
  getCallbackUri(svelteKitAuth, host, state) {
    return this.getUri(svelteKitAuth, `${"/callback/"}${this.id}?state=${state}`, host);
  }
  async getAuthorizationUrl({ url }, auth, state, nonce) {
    const endpoint = "https://api.twitter.com/oauth/authorize";
    const { oauthToken } = await this.getRequestToken(auth, url.host, state);
    const data = {
      oauth_token: oauthToken,
      redirect_uri: this.getCallbackUri(auth, url.host, state),
    };
    const authUrl = `${endpoint}?${new URLSearchParams(data)}`;
    return authUrl;
  }
  async getTokens(oauthToken, oauthVerifier) {
    const endpoint = 'https://api.twitter.com/oauth/access_token';

    const data = {
      oauth_consumer_key: this.config.apiKey,
      oauth_token: oauthToken,
      oauth_verifier: oauthVerifier,
    };

    const response = await fetch(`${endpoint}?${new URLSearchParams(data)}`, { method: 'POST' });
    // This endpoint returns query string like key-value pairs
    // https://developer.twitter.com/en/docs/authentication/api-reference/access_token
    return Object.fromEntries([...(new URLSearchParams(await response.text()))]);
  }
  async getUserProfile({ oauth_token, oauth_token_secret, ...account }) {
    let user = {};
    try {
      user = await (new TwitterApi({
        appKey: this.config.apiKey,
        appSecret: this.config.apiSecret,
        accessToken: oauth_token,
        accessSecret: oauth_token_secret,
      })).readOnly.currentUser();
    } catch (e) {
      console.error('could not get current user: ', e)
      return {};
    }

    return { ...user, ...account, oauth_token, oauth_token_secret };
  }
  async callback(event, auth) {
    const { url } = event;
    const oauthToken = url.searchParams.get("oauth_token");
    const oauthVerifier = url.searchParams.get("oauth_verifier");
    const redirect = this.getStateValue(url.searchParams, "redirect");
    const tokens = await this.getTokens(oauthToken, oauthVerifier);
    let user = await this.getUserProfile(tokens);
    if (this.config.profile) {
      user = await this.config.profile(user, tokens);
    }
    return [user, redirect ?? this.getUri(auth, "/", url.host)];
  }
}

TwitterV2AuthProvider.profileHandler = twitterProfileHandler;

and then incorporate it into my auth configuration:

export const appAuth = new SvelteKitAuth({
  protocol: import.meta.env.VITE_OAUTH_PROTOTCOL,
  providers: [
    new TwitterV2AuthProvider({
      apiKey: import.meta.env.VITE_TWITTER_API_KEY,
      apiSecret: import.meta.env.VITE_TWITTER_API_SECRET,
      profile: (profile, tokens) => {
        const slim = TwitterV2AuthProvider.profileHandler(profile);
        return { ...slim, tokens: { oauth_token: tokens.oauth_token, oauth_token_secret: tokens.oauth_token_secret }, provider: "twitter" };
      },
    }),
  ],
  callbacks: {
  ...