I have built a web app that allows me to login with Spotify and also gain the users access token which is required for the use of the API. However I am unsure on how to insert the users token from their session into the API's headers.
I can see in the console that the authorization request does return a user token and I have verified they do work.
I am using Next-Auth for authenticating the user through Spoitfy.
My current [...nextauth].js file
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
import Spotify from "../../../providers/spotify"
import Twitch from "../../..//providers/twitch"
export default (req, res) => NextAuth(req, res, {
providers: [
Spotify({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
}),
Twitch({
clientId: process.env.TWITCH_CLIENT_ID,
clientSecret: process.env.TWITCH_CLIENT_SECRET,
})
],
callbacks: {
async jwt(token, _, session) {
if (session) {
token.id = session.id
token.accessToken = session.accessToken
}
console.log(token);
return token
},
async session(session, user) {
session.user = user
return session
}
},
})
My current file for making the API request using axios
import axios from "axios";
import { VStack, Heading, Text, Box, Center } from "#chakra-ui/layout";
import { signIn, signOut, useSession } from 'next-auth/client'
const Spotify = ({ Spotify }) => { if(!Spotify){ return <div>Loading...</div> }
// eslint-disable-next-line react-hooks/rules-of-hooks
const [ session, loadings ] = useSession();
console.log(Spotify);
return (
<VStack>
{Spotify.map((Spoty) => (
<div key={Spoty.id}>
<Heading>{Spoty.name}</Heading>
<Text>Link: {Spoty.href}</Text>
<Text>Owner: {Spoty.owner.display_name}</Text>
</div>
))}
</VStack>
);
};
Spotify.getInitialProps = async (ctx) => {
try {
const res = await axios.get("https://api.spotify.com/v1/me/playlists", {
headers: {
Authorization: `Bearer ${session.user.accessToken}`
}
});
const Spotify = res.data.items;
return { Spotify };
} catch (error) {
return { error };
}
};
export default Spotify;
Related
I'm trying to implement Server Side Authentication with Supabase and Sveltekit. I followed the Quickstart Guide and was able to do authentication client-side. (preventDefault on the submit event and do client-side POST request).
But when trying to do the same thing Server-Side , the auth cookie with the token is not created. Here's the logic:
// src/routes/login/+page.server.ts
import type { PostgrestResponse } from '#supabase/supabase-js';
import { supabaseClient } from '$lib/supabaseClient';
import type { Database } from '$lib/types/database.types';
import type { PageLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { redirect } from '#sveltejs/kit';
export const actions: Actions = {
'login-with-password': async ({ request }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');
console.log(email, password);
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
console.log(data);
if (error) {
return {
status: 500,
body: {
error: error.message
}
};
}
throw redirect(302, '/');
return { success: true };
}
};
data seems to hold the correct response, with token and everything, but that's not persisted as a cookie.
https://stackblitz.com/~/github.com/gkatsanos/client
I want to extract account id as additional detail from session callback using Next_auth. Whatevere I add in jwt token and pass it to session callback still it returns the same data. I tried logging out and loggin in many times but all in vain. Any help will be appreciated.
Code in pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
}),
FacebookProvider({
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET
})
],
secret: process.env.SECRET,
session:{
strategy:"jwt"
},
callback: {
async jwt({ token, account, user }) {
// Persist the OAuth access_token to the token right after signin
if (user) {
token.id = account.id;
}
return token
},
async session({ session, token }) {
// Send properties to the client, like an access_token from a provider.
session.user.id = token.id;
return session;
},
// redirect: async(url, _baseUrl) => {
// if (url === "/profile") {
// return Promise.resolve("/");
// }
// return Promise.resolve("/");
// }
}
})
Code in login page
import { useSession, getSession, signIn } from "next-auth/react"
useEffect(() => {
const good = async () => {
const session = await getSession();
if (session) {
console.log(session);
}
}
good();
}, [])
Result I get is
{"user":{"name":"myname","email":"myemail","image":"imageUrl"},"expires":"2022-09-05T11:28:41.840Z"}
This is code in api/auth/[...nextAuth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "#/models/user";
import connectToDb from "#/config/dbConnection";
export default NextAuth({
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
async authorize(credentials) {
connectToDb();
const { email, password } = credentials;
// check if email and passwird entered
if (!email || !password) {
throw new Error("please enter email or password");
}
const user = await User.findOne({
email,
// becasue in pre.save I excluded returning password in api response
}).select("+password");
if (!user) {
throw new Error("Invalid email or password");
}
// check if password is correct
// I added comparePassword to userSchema methods in mongoose
const isPasswordMatched = await user.comparePassword(password);
if (!isPasswordMatched) {
throw new Error("please enter email or password");
}
// creating a promise
return Promise.resolve(user);
},
}),
],
callbacks: {
// jwt callback is only called when token is created
jwt: async ({ token, user }) => {
// user is obj that we have received from authorize Promise.resolve(user)
user && (token.user = user);
// not this token has user property
return Promise.resolve(token);
},
// user arg here is actully token that returned from jwt.
session: async ({ session, token }) => {
// session callback is called whenever a session for that particular user is checked
console.log("user in ...next auth api", token);
session.user = token.user;
// since I get error, I return Promise.resolve(session)
return Promise.resolve(session);
},
},
});
then I write a middleware to get the session:
const isAuthenticatedUser = asyncErrors(async (req, res, next) => {
// when user logged in we create the session of user
// getSession gets auth-token from req.headers
const session = await getSession({ req });
// THIS RETURNS NULL
console.log("session", session);
if (!session) {
return next(new ErrorHandler("Login first to access this resource", 401));
}
req.user = session.user;
next();
});
export { isAuthenticatedUser };
I write api/me to check the user
import nc from "next-connect";
import connectToDb from "#/config/dbConnection";
import { currentUserProfile } from "#/controllers/authController";
import { isAuthenticatedUser } from "#/middlewares/auth";
import onError from "#/middlewares/errors";
const handler = nc({ onError });
connectToDb();
handler.use(isAuthenticatedUser).get(currentUserProfile);
export default handler;
When I visit http://localhost:3000/api/me, since session is logged as null, I get not authneticated, even though, in chrome debugging tools' Aplication tab next-auth credentials are present
Since https://next-auth.js.org/getting-started/upgrade-v4
Version 4 makes using the SessionProvider mandatory.
this is _app.js component
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
// `session` comes from `getServerSideProps` or `getInitialProps`.
// Avoids flickering/session loading on first load.
<SessionProvider session={session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
)
}
This problem is happening due to not specifying e.preventDefault() on login button.
The working code should look like this :-
async function login(e) {
e.preventDefault(); //Add this to your code.
const getLoginStatus = await signIn("credentials", {
redirect: false,
username,
password,
})};
I am trying to implement Google OAuth with MERN Stack.
There was a nice blog about this like as below.
https://medium.com/#alexanderleon/implement-social-authentication-with-react-restful-api-9b44f4714fa
I used react-google-login for the front-end and passport, passport-google-token, and jsonwebtoken for the back-end.
It was all good until express js got the accessToken and created a User document, but I actually don't know what is the next step to keep login and authenticate it. This question is mentioned in the above article, but he hasn't implemented it.
Is there any information to solve this?
"Step 7: Return a JWT token to the frontend. What you do with that token is out of scope for this tutorial, but it should probably be used to authenticate each of the logged-in user’s actions."
My react side code is as below
import React from 'react';
import GoogleLogin from 'react-google-login';
import api from '../config/api';
import { useGlobalState } from '../config/globalState';
import { AUTH_SIGN_IN, SET_USER } from '../config/types';
const Navbar = () => {
const { state, dispatch } = useGlobalState();
const { isLoggedIn } = state;
const responseGoogle = async (data) => {
try {
const { accessToken } = data;
const res = await api.post('/api/auth/signin', {
access_token: accessToken
});
console.log('res.data:', res.data);
dispatch({ type: AUTH_SIGN_IN });
} catch (err) {
console.log('err: ', err.message);
}
};
return (
<div>
This is the title
{
!isLoggedIn &&
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
buttonText="Login with Google"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
}
</div>
)
}
export default Navbar
This is passport.js
const passport = require('passport');
const GooglePlusTokenStrategy = require('passport-google-token').Strategy;
const User = require('../models/user');
module.exports = passport => {
passport.use('googleToken', new GooglePlusTokenStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}, async (accessToken, refreshToken, profile, done) => {
console.log('accessToken:', accessToken);
console.log('refreshToken:', refreshToken);
console.log('profile:', profile);
try {
const foundUser = await User.findOne({ googleId: profile.id})
if (foundUser) {
console.log('User already exists');
return done(null, foundUser);
} else {
const newUser = await User.create({
googleId: profile.id,
displayName: profile.displayName,
email: profile.emails[0].value,
photo: profile.photos[0].value
});
await newUser.save();
return done(null, newUser);
}
} catch(error) {
done(error, false, error.message);
}
}));
}
This is the /api/auth/signin route handling part.
const JWT = require('jsonwebtoken');
const User = require('../models/user');
const signToken = user => {
return JWT.sign({
iss: 'AnySecret',
sub: user.id,
iat: new Date().getTime(),
exp: new Date().setDate(new Date().getDate() + 1)
}, process.env.JWT_SECRET);
}
module.exports = {
signIn: async (req, res) => {
console.log('signIn called');
const token = signToken(req.user);
res.cookie('access_token', token, {
httpOnly: true
});
res.status(200).send({
user: req.user,
token
});
}
}
I'm trying to implement authentication using Auth0 in a react-admin v3 app. I need to implement an authProvider that talks with Auth0. This sounds like something that should be available somewhere, but the closest I could find was https://github.com/alexicum/merge-admin/blob/master/src/Auth/index.js, which is about 2 years old (the SDKs have changed since then).
Is there an Auth0 authProvider somewhere I can reuse, or do I have to implement it myself?
Thanks!
for reference sake, here's an example of a way to integrate react admin with auth0-react package
index.js
import { Auth0Provider } from "#auth0/auth0-react";
ReactDOM.render(
<Auth0Provider
domain="XXXXX.auth0.com"
clientId="XXXXX"
audience="https://XXXXX"
redirectUri={window.location.origin}
>
<React.StrictMode>
<App />
</React.StrictMode>
</Auth0Provider>,
document.getElementById("root")
);
App.js
import { withAuth0, withAuthenticationRequired } from "#auth0/auth0-react";
import ApolloClient from "apollo-boost";
// I'm using Hasura w/ JWT Auth, so here's an example of how to set Authorization Header
async componentDidMount() {
const token = await this.props.auth0.getAccessTokenSilently();
const client = new ApolloClient({
uri: "https://HASURA_URL/v1/graphql",
headers: {
Authorization: `Bearer ${token}`
},
});
buildHasuraProvider({ client }).then((dataProvider) =>
this.setState({ dataProvider })
);
}
export default withAuthenticationRequired(withAuth0(App));
I've created a sample application with Auth0 and react-admin way of auth
https://github.com/spintech-software/react-admin-auth0-example
Here is auth provider code for reference
import authConfig from "./authConfig";
import {Auth0Client} from '#auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: authConfig.domain,
client_id: authConfig.clientID,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
const CallbackURI = "http://localhost:3000/login"
export default {
// called when the user attempts to log in
login: (url) => {
if (typeof url === 'undefined') {
return auth0.loginWithRedirect({
redirect_uri: CallbackURI
})
}
return auth0.handleRedirectCallback(url.location);
},
// called when the user clicks on the logout button
logout: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) { // need to check for this as react-admin calls logout in case checkAuth failed
return auth0.logout({
redirect_uri: window.location.origin,
federated: true // have to be enabled to invalidate refresh token
});
}
return Promise.resolve()
})
},
// called when the API returns an error
checkError: ({status}) => {
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) {
return Promise.resolve();
}
return auth0.getTokenSilently({
redirect_uri: CallbackURI
})
})
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => {
return Promise.resolve()
},
};
My answer is following react-admin approach where I use its authProvider like below. There are two main steps:
Get needed data from useAuth0 hook.
Convert authProvider into function where it takes the above values, and return an object like default.
// In App.js
import authProvider from './providers/authProvider';// my path is changed a bit
const App = () => {
const {
isAuthenticated,
logout,
loginWithRedirect,
isLoading,
error,
user,
} = useAuth0();
const customAuthProvider = authProvider({
isAuthenticated,
loginWithRedirect,
logout,
user,
});
return (
<Admin
{...otherProps}
authProvider={customAuthProvider}
>
{...children}
</Admin>
);
}
// My authProvider.js
const authProvider = ({
isAuthenticated,
loginWithRedirect,
logout,
user,
}) => ({
login: loginWithRedirect,
logout: () => logout({ returnTo: window.location.origin }),
checkError: () => Promise.resolve(),
checkAuth: () => (isAuthenticated ? Promise.resolve() : Promise.reject()),
getPermissions: () => Promise.reject('Unknown method'),
getIdentity: () =>
Promise.resolve({
id: user.id,
fullName: user.name,
avatar: user.picture,
}),
});
export default authProvider;
That's it.
It's more convenient to wrap the react-admin app with auth0 native login, and then provide react-admin dataProvider an http client that reads the jwt token stored in local storage by auth0.