I have a SPA react application where I am using auth0 for authentication. I would like to do a silent authentication and get a new token whenever site is refreshed, like it is suggested in this answer.
I have an Auth class responsible for handling tokens:
import auth0 from 'auth0-js'
import { authConfig } from '../config'
export default class Auth {
accessToken
idToken
expiresAt
auth0 = new auth0.WebAuth({
domain: authConfig.domain,
clientID: authConfig.clientId,
redirectUri: authConfig.callbackUrl,
responseType: 'token id_token',
scope: 'openid'
})
constructor(history) {
this.history = history
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
this.handleAuthentication = this.handleAuthentication.bind(this)
this.isAuthenticated = this.isAuthenticated.bind(this)
this.getAccessToken = this.getAccessToken.bind(this)
this.getIdToken = this.getIdToken.bind(this)
this.renewSession = this.renewSession.bind(this)
}
login() {
this.auth0.authorize()
}
handleAuthentication() {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
console.log('Access token: ', authResult.accessToken)
console.log('id token: ', authResult.idToken)
this.setSession(authResult)
} else if (err) {
this.history.replace('/')
console.log(err)
alert(`Error: ${err.error}. Check the console for further details.`)
}
})
}
getAccessToken() {
return this.accessToken
}
getIdToken() {
return this.idToken
}
setSession(authResult) {
// Set isLoggedIn flag in localStorage
localStorage.setItem('isLoggedIn', 'true')
// Set the time that the access token will expire at
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime()
this.accessToken = authResult.accessToken
this.idToken = authResult.idToken
this.expiresAt = expiresAt
// navigate to the home route
this.history.replace('/')
}
renewSession(cb) {
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
cb(err, authResult)
} else if (err) {
this.logout()
console.log(
`Could not get a new token (${err.error}: ${err.error_description}).`
)
}
})
}
logout() {
// Remove tokens and expiry time
this.accessToken = null
this.idToken = null
this.expiresAt = 0
// Remove isLoggedIn flag from localStorage
localStorage.removeItem('isLoggedIn')
this.auth0.logout({
return_to: `${window.location.origin}/login`
})
}
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = this.expiresAt
return new Date().getTime() < expiresAt
}
}
In my main App component in componentDidMount I am calling the renewSession method from Auth class:
export default class App extends Component<AppProps, AppState> {
constructor(props: AppProps) {
super(props)
this.handleLogin = this.handleLogin.bind(this)
this.handleLogout = this.handleLogout.bind(this)
this.createNewPost = this.createNewPost.bind(this)
}
state: AppState = {
tokenRenewed: false
}
componentDidMount() {
this.props.auth.renewSession(() => {
this.setState({ tokenRenewed: true })
})
}
handleLogin() {
this.props.auth.login()
}
handleLogout() {
this.props.auth.logout()
}
async createNewPost() {
const idToken = this.props.auth.getIdToken()
try {
const newPost = await createPost(idToken)
this.props.history.push(`/posts/${newPost.postId}/edit`)
} catch {
alert('Post creation failed')
}
}
render() {
if (!this.state.tokenRenewed) return 'loading...'
const userAuthenticated = this.props.auth.isAuthenticated()
return (
<div>
<Segment vertical>
<Grid container stackable verticalAlign="middle">
<Grid.Row>
<Grid.Column width={16}>
<Router history={this.props.history}>
{this.generateMenu(userAuthenticated)}
{this.generateCurrentPage(userAuthenticated)}
</Router>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</div>
)
}
And this are my routes:
<Router history={history}>
<Switch>
<Route
path="/callback"
render={props => {
handleAuthentication(props)
return <Callback />
}}
/>
<Route
path="/login"
render={props => {
return <LogIn auth={auth} {...props} />
}}
/>
<Route
path="/"
render={props => {
return <App auth={auth} {...props} />
}}
/>
</Switch>
</Router>
That works fine if I login with username/password. But, if I use a social login like Google/Gmail then whenever I login in to the app, I get an error from auth0.checkSession in Auth class:
Could not get a new token (login_required: Login required).
How can I make this work with Google/Gmail login as well?
Related
Good morning. I am using NextJS and get the session in the getServerSideProps block.
I am passing multiple parameters to the return object, however, the session does not pass. All of the other key value pairs works, but when I try to log the session, It comes undefined... I don't understand...
const DashboardPage = ({ session, secret }) => {
const [loading, setloading] = useState(false);
//This does not work
console.log('Session: ', session);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={session?.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
// get sessions with added user Id in the session object
const session = await requireAuthentication(context);
// This works
console.log(session);
if (!session) {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
else {
return {
props: {
session: session,
secret: 'Pasarika este dulce'
}
}
}
}
export default DashboardPage;
The purpose of the requireAuthentication function is to create a new session object where I will insert another key value pair that will contain the user id and the session that will be used in the entire app.
In that function I get the user by email that is returned by the session and get the Id from the db. I than return the new session object that looks like this:
{
user: {
name: 'daniel sas',
email: 'email#gmail.com',
image: 'https://lh3.googldeusercontent.com/a/A6r44ZwMyONqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'//This is the required thing in my app
},
expires: '2022-12-23T08:04:08.263Z'
}
The following function is used to get the data from the database
import { getSession } from "next-auth/react";
import prisma from './prisma'
// This function get the email and returns a new session object that includes
// the userId
export const requireAuthentication = async context => {
const session = await getSession(context);
// If there is no user or there is an error ret to signup page
if (!session) return null
// If the user is not found return same redirect to signup
else {
try {
const user = await prisma.user.findUnique({where: { email: session.user.email }});
if (!user) return null;
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
return newSession;
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}
Hi we have purchased a theme. In the theme they have include JWT based login but used fake db(dummy values) and local storage. I want to change it to our logic which uses JWT based authentication. Backend used is spring boot.
I could not fully understand the code here. In the docs, they mentioned to make changes in the authcontext file only. However, it didnt worked after I made changes in the handlelogin function(authcontext.js file). Also I gave the loginEndpoint as the backend API URL, but not sure what to replace in place of meEndpoint. If anybody gets any better idea, please try to help. Will it work properly if me make changes in the handleLogin function of authContext.js file alone? or do we have to make changes in the initAuth async function that is defined inside useEffect hook? Also what is the onSubmit function(the function which executes when login button is clicked) doing
please find the code in AuthContext.js file
import { createContext, useEffect, useState } from 'react'
// ** Next Import
import { useRouter } from 'next/router'
// ** Axios
import axios from 'axios'
// ** Config
import authConfig from 'src/configs/auth'
// ** Defaults
const defaultProvider = {
user: null,
loading: true,
setUser: () => null,
setLoading: () => Boolean,
isInitialized: false,
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
setIsInitialized: () => Boolean,
register: () => Promise.resolve(),
token:null,
setToken:()=>null
}
const AuthContext = createContext(defaultProvider)
const AuthProvider = ({ children }) => {
// ** States
const [user, setUser] = useState(defaultProvider.user)
const [loading, setLoading] = useState(defaultProvider.loading)
const [isInitialized, setIsInitialized] = useState(defaultProvider.isInitialized)
const [token,setToken]=useState(defaultProvider.token)
// ** Hooks
const router = useRouter()
useEffect(() => {
const initAuth = async () => {
setIsInitialized(true)
const storedToken = window.localStorage.getItem(authConfig.storageTokenKeyName)
if (storedToken) {
setLoading(true)
await axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: storedToken
}
})
.then(async response => {
setLoading(false)
setUser({ ...response.data.userData })
})
} else {
setLoading(false)
}
}
initAuth()
}, [])
const handleLogin = (params, errorCallback) => {
axios
.post(authConfig.loginEndpoint, params)
.then(async res => {
window.localStorage.setItem(authConfig.storageTokenKeyName, res.data.accessToken)
})
.then(() => {
axios
.get(authConfig.meEndpoint, {
headers: {
Authorization: window.localStorage.getItem(authConfig.storageTokenKeyName)
}
})
.then(async response => {
const returnUrl = router.query.returnUrl
setUser({ ...response.data.userData })
await window.localStorage.setItem('userData', JSON.stringify(response.data.userData))
const redirectURL = returnUrl && returnUrl !== '/' ? returnUrl : '/'
router.replace(redirectURL)
})
})
.catch(err => {
if (errorCallback) errorCallback(err)
})
}
const values = {
user,
loading,
setUser,
setLoading,
isInitialized,
setIsInitialized,
login: handleLogin,
logout: handleLogout,
register: handleRegister
}
return <AuthContext.Provider value={values}>{children}</AuthContext.Provider>
}
export { AuthContext, AuthProvider }
the authConfig file as below:-
export default {
meEndpoint: '/auth/me',
loginEndpoint: 'qortex-dev-backoffice-portal.westus2.cloudapp.azure.com:8080/auth-user/login',
registerEndpoint: '/jwt/register',
storageTokenKeyName: 'accessToken'
}
the handlesubmit function executes on login button click as shown below
const onSubmit = data => {
const { email, password } = data
auth.login({ email, password }, () => {
setError('email', {
type: 'manual',
message: 'Email or Password is invalid'
})
})
}
Login form controls code as shown below
<form noValidate autoComplete='off' onSubmit={handleSubmit(onSubmit)}>
<FormControl fullWidth sx={{ mb: 4 }}>
<Controller
name='email'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<TextField
autoFocus
label='Email'
value={value}
onBlur={onBlur}
onChange={onChange}
error={Boolean(errors.email)}
placeholder='admin#materio.com'
/>
)}
/>
{errors.email && <FormHelperText sx={{ color: 'error.main' }}>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl fullWidth>
<InputLabel htmlFor='auth-login-v2-password' error={Boolean(errors.password)}>
Password
</InputLabel>
<Controller
name='password'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<OutlinedInput
value={value}
onBlur={onBlur}
label='Password'
onChange={onChange}
id='auth-login-v2-password'
error={Boolean(errors.password)}
type={showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position='end'>
<IconButton
edge='end'
onMouseDown={e => e.preventDefault()}
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
)}
/>
{errors.password && (
<FormHelperText sx={{ color: 'error.main' }} id=''>
{errors.password.message}
</FormHelperText>
)}
</FormControl>
I am making authentication with SvelteKit and Laravel. This is the flow i currently have:
User logs in with correct credentials.
User login route has no middleware enabled on the Laravel side.
This login request returns a JWT token, which gets send back to the Sveltekit server.
I set this token as a cookie using this code:
const headers = {
'Set-Cookie': cookie.serialize(variables.authCookieName, body.token, {
path: '/',
httpOnly: true,
sameSite: 'lax'
})
}
return {
headers,
body: {
user
}
}
The cookie is correctly set after that, verified.
So the authentication is handled correctly. But now i want to send that cookie with Axios to the Laravel server and authenticate the user but that doesn't work. The Laravel server never receives the cookie. The Axios withCredentials setting also never sends that cookie to the Laravel server. How can i make it work so that the cookie header is sent with Axios to Laravel? I have 0 CORS errors in my browser so i don't think that is the issue.
My API Class in SvelteKit:
import axios from 'axios'
import { variables } from '$lib/variables'
const headers: Record<string, string | number | boolean> = {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
class Api {
constructor() {
axios.defaults.baseURL = variables.apiUrl
axios.defaults.withCredentials = true
axios.interceptors.response.use(
response => response.data,
error => Promise.reject(error.response.data)
)
}
get(url: string) {
return axios.get(url, { headers })
}
post(url: string, data?: unknown) {
return axios.post(url, data, { headers })
}
patch(url: string, data: Record<string, unknown>) {
return axios.patch(url, data, { headers })
}
}
const api = new Api()
export default api
My Userservice:
import api from '$core/api'
const resource = '/users'
const userService = () => {
const getAll = async () => {
return await api.get(resource)
}
return {
getAll
}
}
export default userService
The Index endpoint (routes/dashboard/index.ts)
import services from '$core/services'
export async function get() {
return await services.user.getAll()
.then(({ data }) => {
return {
body: { users: data.users }
}
}).catch((err) => {
return {
body: { error: err.message }
}
})
}
My Hooks.index.ts (maybe for reference)
import * as cookie from 'cookie'
import jwt_decode from 'jwt-decode'
import type { GetSession, Handle } from '#sveltejs/kit'
import type { User } from '$interfaces/User'
// This is server side
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const { jwt } = cookie.parse(event.request.headers.get('cookie') || '')
if (jwt) {
const { user } = jwt_decode<{ user: User }>(jwt)
if (user) {
event.locals.user = user
}
}
return resolve(event)
}
export const getSession: GetSession = async (request) => {
return {
user: request.locals.user
}
}
Can someone help or explain why Axios has no idea if the cookie is set or not, or how i can send the Cookie with the request to the Laravel Server?
I am trying to setup authentication in an app using React-Apollo and Next.js. It is setup to return a cookie containing a jwt token after sending in the credentials, which works perfectly well. When trying to run a query for the current user, it comes back as null. If I refresh the page, the query works and I get back the current user. I did notice that the cache is updated with the correct data even though the response is null.
Component:
class Index extends Component{
constructor(props){
super(props)
this.usernameInput = ''
this.passwordInput = ''
this.state = {
error: null,
}
}
getCurrentAccount = async () => {
return await this.props.getCurrentAccount
}
sendCreds = async (e) => {
e.preventDefault()
const result = await this.props.authenticate({
variables: {
username: this.usernameInput,
password: this.passwordInput,
},
update: (store, {data: { currentAccount } } ) => {
const data = store.readQuery({
query: QUERY_CURRENT_ACCOUNT,
})
console.log('after authenitcation', data)
}
})
if (result.error) {
console.log('error on authentication', result.error)
this.setState({error: 'There was an error with our server.'})
} else if (result) {
//this.login()
}
}
login = async () => {
const data = await this.getCurrentAccount()
if (data.error) {
console.log('error on fetching account', data.error)
this.setState({error: 'There was an error with our server.'})
} else if (data.currentAccount.role) {
Router.push('/dashboard')
} else {
this.setState({error: 'The ID or password you entered is incorrect.'})
}
}
render() {
return (
<form onSubmit={this.sendCreds}>
{ this.state.error ? <FormError>{this.state.error}</FormError> : null}
<fieldset>
<InputField id="username" label="usrname" placeholder="username" pattern="^[a-zA-Z0-9_]*$" required inputValue={val=>this.usernameInput = val}/>
<InputField id="password" type="password" label="Password" placeholder="Password" required inputValue={val=>this.passwordInput = val}/>
</fieldset>
<ButtonPrimary type="submit" label="Send Credentials" width="full"/>
<ButtonPrimary label="Get Current User to Login" width="full" clickAction={()=>this.login()}/>
<footer>
<Link href="forgot-username">
<a>Forgot my username</a>
</Link>
<Link href="forgot-password">
<a>Forgot my password</a>
</Link>
</footer>
</form>
)}
}
GrahpQL:
const MUTATION_AUTH = gql`
mutation authenticate($username: String!, $password: String!) {
authenticate(input: {
username: $username,
password: $password
}) {
jwtToken
}
}
`
const QUERY_CURRENT_ACCOUNT = gql`
{
currentAccount {
account {
firstName
profileImageUrl
}
role
}
}
`
export default withData(compose(
graphql(MUTATION_AUTH, { name: 'authenticate' }),
graphql(QUERY_CURRENT_ACCOUNT, { name: 'getCurrentAccount'} )
)
(withApollo(Index)))
I am new in react, and try to make my first project using such features: react, redux, react-router, redux-thunk. I am fetching data from url with json. It works fine on powerfull pc, on wicker it will not work becouse as i understud, it is starts to fetch then it try to render components without data and only then it gets data from url... Also same result i have when i refresh innerpage, it will try to render components before it get data.
So here is creating of store:
const middleware = [routerMiddleware(hashHistory)];
const store = createStore( combineReducers({
reducers:reducers,
routing:routerReducer
}),compose(applyMiddleware(...middleware, thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()));
const history = syncHistoryWithStore(hashHistory, store);
Then here is my provider:
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={withTransition(App)}>
<IndexRoute component={Projects} />
<Route path="project/:id/" component={SingleProject}>
</Route>
</Route>
</Router>
</ Provider>,
document.getElementsByClassName('root')[0]
)
I am fetching data in that way:
function fetchProjRequest(){
return {
type: "FETCH_PROJ_REQUEST"
}
}
function fetchProjSuccess(payload) {
return {
type: "FETCH_PROJ_SUCCESS",
payload
}
}
function fetchProjError() {
return {
type: "FETCH_PROJ_ERROR"
}
}
function fetchProj() {
const URL = "http://****.com/data/proj";
return fetch(URL, { method: 'GET'})
.then( response => Promise.all([response, response.json()]));
}
class App extends Component{
constructor(props){
super(props);
this.props.fetchProjWithRedux();
}
render(){
return (
<div className={styles.app}>
<Logo />
<TagFilter />
<div className={styles.content}>
{this.props.children}
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
proj: state.proj
}
}
export default connect(
state => ({
proj: state.reducers.projects.proj
}),
dispatch =>({
fetchProjWithRedux: () => {
fetchProj().then(([response, json]) =>{
if(response.status === 200){
dispatch(fetchProjSuccess(json))
}
else{
dispatch(fetchProjError())
}
})
},
})
)(App);
It would be greate if someone of you tell me were i was wrong :( It is very imortant for me!
Here is a gist of a hoc that takes care of what you need.
Make sure to introduce a isDataLoaded boolean prop in your reducer and make it true when FETCH_PROJ_SUCCESS is called. Hope it helps.
Some changes to your code:
import dataLoader from './dataLoader';
const AppWithLoader = dataLoader(App);
export default connect(
state => ({
isDataLoaded: state.proj.isDataLoaded,
proj: state.reducers.projects.proj
}),
dispatch =>({
dispatchGetData: () => {
fetchProj().then(([response, json]) =>{
if(response.status === 200){
dispatch(fetchProjSuccess(json))
}
else{
dispatch(fetchProjError())
}
})
},
})
)(AppWithLoader);