I am working on authentication using Auth0 and react. I am using loginWithPopup() for the login popup screen. But every time I end up getting misconfiguration error(like you can see in the attachment). But if I remove the response_mode = web_message from the URL it works, is there any way to remove response_mode from code. I am using the react-auth0-spa.js given my auth0 quick start
import React, { Component, createContext } from 'react';
import createAuth0Client from '#auth0/auth0-spa-js';
// create the context
export const Auth0Context = createContext();
// create a provider
export class Auth0Provider extends Component {
state = {
auth0Client: null,
isLoading: true,
isAuthenticated: false,
user: false,
};
config = {
domain: "dev-ufnn-q8r.auth0.com",
client_id: "zZh4I0PgRLQqLKSPP1BUKlnmfJfLqdoK",
redirect_uri: window.location.origin,
//audience: "https://reachpst.auth0.com/api/v2/"
};
componentDidMount() {
this.initializeAuth0();
}
// initialize the auth0 library
initializeAuth0 = async () => {
const auth0Client = await createAuth0Client(this.config);
const isAuthenticated = await auth0Client.isAuthenticated();
const user = isAuthenticated ? await auth0Client.getUser() : null;
this.setState({ auth0Client, isLoading: false, isAuthenticated, user });
};
loginWithPopup = async () => {
try {
await this.state.auth0Client.loginWithPopup();
}
catch (error) {
console.error(error);
}
this.setState({
user: await this.state.auth0Client.getUser(),
isAuthenticated: true,
});
};
render() {
const { auth0Client, isLoading, isAuthenticated, user } = this.state;
const { children } = this.props;
const configObject = {
isLoading,
isAuthenticated,
user,
loginWithPopup: this.loginWithPopup,
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
logout: (...p) => auth0Client.logout(...p)
};
return (
<Auth0Context.Provider value={configObject}>
{children}
</Auth0Context.Provider>
);
}
}
After a bit of research, I found an answer to my own question. So if we use response_mode = web_message then we need to configure our callback URL in allowed web origin field as well. In my case, I am using loginWithPopup() so which typically adds response_mode = web_message in the login URL because loginWithPopup() from auth0 SDK is a combination of PKCE + web_message
https://auth0.com/docs/protocols/oauth2 (under how response mode works?)
https://auth0.com/blog/introducing-auth0-single-page-apps-spa-js-sdk (under behind the curtain)
Related
I am trying to make an ecommerce using react, redux toolkit and axios
the problem is that I want the user to log in and get his cart from the backend right after the login
it always fails the and says (unauthorized) when i first login because it can't find the token
then after refresh it says unauthorized one more time
after the third refresh it works
this is my get cart
export const getCart = createAsyncThunk("cart/getcart", async () => {
const response = await axios.get("http://127.0.0.1:8000/techcart/get_cart/", {
headers: {
Authorization: `Token ${token}`,
},
});
return response.data;
});
const cartSlice = createSlice({
name: "cart",
initialState: {
cart: [],
cartItemsIds :[],
},
builder.addCase(getCart.fulfilled, (state, action) => {
state.cart = action.payload;
and this is my login function
export const login = createAsyncThunk(
"auth/login",
async ({ email, password }, thunkAPI) => {
try {
const response = await axios.post(
"http://127.0.0.1:8000/techcart/login/",
{ username: email, password }
);
localStorage.setItem("user", JSON.stringify(response.data));
return response.data;
} catch (error) {}
}
);
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
builder.addCase(login.fulfilled, (state, action) => {
state.isLoggedIn = true;
state.user = action.payload;
here is where i am doing the login
const HandleLogin = () => {
dispatch(login({ email, password }));
};
useEffect(()=> {
if(isLoggedIn){
navigate('/')
dispatch(getCart())
}
},[isLoggedIn])
Cart page
useEffect(() => {
dispatch(getCart());
}, []);
here is where im defining my token :
export let user = JSON.parse(localStorage.getItem("user")) ? JSON.parse(localStorage.getItem("user")) : null;
export let userId = user ? user.user_id : null;
export let token = user!=null ? user.token : null;
and here is where im importing it in my cart slice
import { user, token } from "../../constants";
im using redux persist to persist the state of my cart
if anyone can help me i'm so thankful
here is what happens
You're initializing your token directly when your js is executed. So when you retrieve it, it is undefined.
Ans when you do the login, you're indeed storing your token, but you're not updating it in your application.
I can see you're using redux, so store your token in your redux store, and before sending your api call to retrieve your cart, retrieve your token from redux, to always have the latest value of your 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.
So I am trying to use cognito to manage authentication in my react application, with the identity provider being SAML. This is working very smoothly in Chrome and Firefox, but not in IE 11. Here is I set up my Auth:
import { Component } from 'react';
import { connect } from 'react-redux';
import { CognitoAuth } from 'amazon-cognito-auth-js';
import { signIn, signOutSuccess } from '../store/auth';
import { setupAxios } from '../axios';
import {
AWS_COGNITO_CLIENT_ID,
AWS_COGNITO_USER_POOL_ID,
AWS_COGNITO_REDIRECT_SIGN_IN,
AWS_COGNITO_REDIRECT_SIGN_OUT,
AWS_COGNITO_APP_WEB_DOMAIN
} from '../env';
const cognitoSetup = props => {
//as per documentation
const authData = {
ClientId: AWS_COGNITO_CLIENT_ID,
TokenScopesArray: ['email', 'openid', 'profile'],
RedirectUriSignIn: AWS_COGNITO_REDIRECT_SIGN_IN,
RedirectUriSignOut: AWS_COGNITO_REDIRECT_SIGN_OUT,
AppWebDomain: AWS_COGNITO_APP_WEB_DOMAIN,
IdentityProvider: 'SAML',
UserPoolId: AWS_COGNITO_USER_POOL_ID
};
const auth = new CognitoAuth(authData);
auth.useCodeGrantFlow(); //getting the refresh token
auth.userhandler = {
onSuccess: result => {
const { profile, name, family_name, email } = result.idToken.payload;
//passes role to store for use in the rest of the app
const username = result.idToken.payload.identities[0].userId;
const fullName = `${name} ${family_name}`;
props.signIn({ username, profile, fullName, email });
},
onFailure: function(err) {
console.error(err);
throw err;
}
};
return auth;
};
export class AuthService extends Component {
constructor(props) {
super(props);
this.authService = cognitoSetup(this.props);
//passes the auth to axios to check for token on request
setupAxios(this.authService);
}
componentDidMount() {
const curUrl = window.location.href;
if (curUrl.includes('?code=')) {
this.authService.parseCognitoWebResponse(curUrl);
} else if (!curUrl.includes('?error')) {
this.authService.getSession();
}
}
signOut = async () => {
await this.authService.signOut();
};
async componentDidUpdate(prevProps) {
if (prevProps.shouldSignOut !== this.props.shouldSignOut) {
if (this.props.shouldSignOut) {
await this.signOut();
this.props.signOutSuccess();
}
}
}
//render nothing
render() {
return null;
}
}
const mapState = state => ({
username: state.auth.username,
signedIn: state.auth.signedIn,
shouldSignOut: state.auth.shouldSignOut
});
const mapDispatch = dispatch => ({
signIn: (username, profile) => dispatch(signIn(username, profile)),
signOutSuccess: () => dispatch(signOutSuccess())
});
export default connect(mapState, mapDispatch)(AuthService);
This AuthService.js is rendered upon loading the application. However When loading in IE11, there is an error var jsonDataObject = JSON.parse(jsonData); invalid character.
I have no idea why this is happening. I have investigated and came to the conclusion that this is going on within the package amazon-cognito-auth-js. I'm under the impression this package was made by amazon so I believe the package is not at fault, but I cannot see anyone else with this issue. Does anyone have any suggestions?
EDIT: I do have a polyfill
I saw you used arrow functions => in your code which is not supported by IE. You could use babel to compile it and any other ES6 syntax to ES5. For example, compile:
const cognitoSetup = props => {
...
}
to:
var cognitoSetup = function cognitoSetup(props) {
...
}
Besides, have you imported react-app-polyfill at the first line in your src/index.js? This is required for react app to run in IE 11. You could refer to the answer in this thread for detailed steps.
I am trying to get ReactJS working with IdentityServer4 via oidc-client-js library.
PROBLEM
I click the login button and I get redirected to IdentityServer4, once I login, I get redirect to /login-callback and then from there I get redirected to / but I get the following error in the console:
I am not sure what I'm doing wrong, but I've tried all sorts and nothing seems to work.
CODE
All code is open-source and sits here.
App.jsx
// other code ommited, but this is a route
<AuthHandshake path="/login-callback" />
AuthHandshake.jsx
import React from "react";
import { AuthConsumer } from "../AuthProvider/AuthProvider";
function AuthHandshake() {
return <AuthConsumer>{value => value.loginCallback()}</AuthConsumer>;
}
export default AuthHandshake;
AuthProvider.jsx
import React, { useState } from "react";
import { navigate } from "#reach/router";
import { UserManager, WebStorageStateStore } from "oidc-client";
import AuthContext from "../../contexts/AuthContext";
import { IDENTITY_CONFIG } from "../../utils/authConfig";
IDENTITY_CONFIG.userStore = new WebStorageStateStore({
store: window.localStorage
});
const userManager = new UserManager(IDENTITY_CONFIG);
const login = () => {
console.log("Login button click handled.");
userManager.signinRedirect();
};
const logout = () => {
userManager.signoutRedirect();
};
export function AuthProvider(props) {
const [user, setUser] = useState(null);
const loginCallback = () => {
userManager.signinRedirectCallback().then(
user => {
window.history.replaceState(
{},
window.document.title,
window.location.origin
);
setUser(user);
navigate("/");
},
error => {
console.error(error);
}
);
};
const providerValue = {
login: login,
logout: logout,
loginCallback: loginCallback,
isAuthenticated: user
};
const Provider = () => {
if (user) {
return (
<AuthContext.Provider value={providerValue}>
{props.children}
</AuthContext.Provider>
);
} else {
return <div className="auth-provider">{props.children}</div>;
}
};
return (
<>
<AuthContext.Provider value={providerValue}>
{props.children}
</AuthContext.Provider>
</>
);
}
export const AuthConsumer = AuthContext.Consumer;
On the IdentityServer side, I have set the post logout redirect to the same thing /login-callback
new Client
{
ClientId = "bejebeje-react-local",
ClientName = "Bejebeje ReactJS SPA Client",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
RequireConsent = false,
RedirectUris = { "http://localhost:1234/login-callback" },
PostLogoutRedirectUris = { "http://localhost:1234/logout-callback" },
AllowedCorsOrigins = { "http://localhost:1234" },
AllowedScopes = { "openid", "profile", "bejebeje-api-local" },
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
}
Where am I going wrong?
Try this here..
https://github.com/JwanKhalaf/Bejebeje.React/blob/bug/fix-no-code-response-on-redirect/src/components/AuthProvider/AuthProvider.jsx#L15
Basically, when you are sending a login request from the client (OIDC-client.js), the client sends a unique identifier (State) along with the login request in the URL. The client saves this value in the storage option you choose (local/session). After successful sign-in, server issues tokens in the response redirect url and it also includes the unique identifier client initially sent in login request. Watch the login request url and response url in chrome Developer Tools.
When OIDC Client Library receives the sign-in response, it grabs the unique identifier from the URL and matches with the value it kept in the local/session storage at the time of login request. If they don't match, the client will not accept the token issued by the server.
This is just one level of additional security to make sure, the client is not accepting any tokens issued by any random server or any bookmarked URL.
Hope this helps.!!
usermanager.signinRedirect({ state: { bar: 15 } });
I had to add response_mode: 'query' to the UserManager.
var mgr = new Oidc.UserManager({ response_mode: 'query' });
mgr.signinRedirectCallback().then(res => {
window.location = "/signin-callback";
}).catch(error => {
window.location = "/";
})
I am using Apollo 2.0 to manage my graphQL API calls and to handle the global state of my react application.
I am trying to create a login screen where a user enters their username and password, this gets sent to my API to authenticate and upon success, I want to then set the global state of isLoggedIn to true.
So far, I am able to set the global state with one mutation which utilises the #client declaration so it is only concerned with local state. I have another mutation which makes the graphQL API call and validates username / password and then returns success / error responses.
I want to be able to set isLoggedIn once the API call mutation has completed or failed.
My client has the following default state and resolvers set like so:
const httpLink = new HttpLink({
uri: '/graphql',
credentials: 'same-origin'
});
const cache = new InMemoryCache();
const stateLink = withClientState({
cache,
resolvers: {
Mutation: {
updateLoggedInStatus: (_, { isLoggedIn }, { cache }) => {
const data = {
loggedInStatus: {
__typename: 'LoggedInStatus',
isLoggedIn
},
};
cache.writeData({ data });
return null;
},
},
},
defaults: {
loggedInStatus: {
__typename: 'LoggedInStatus',
isLoggedIn: false,
},
},
});
const link = ApolloLink.from([stateLink, httpLink])
const client = new ApolloClient({
link,
cache
});
export default client
Then in my Login component I have the following mutations and queries which I pass as a HOC with the help of compose:
const UPDATE_LOGGED_IN_STATUS = gql`
mutation updateLoggedInStatus($isLoggedIn: Boolean) {
updateLoggedInStatus(isLoggedIn: $isLoggedIn) #client
}`
const AUTHENTICATE = gql`
mutation authenticate($username: String!, $password: String!) {
auth(username: $username, password: $password) {
username
sales_channel
full_name
roles
}
}`
const GET_AUTH_STATUS = gql`
query {
loggedInStatus #client {
isLoggedIn
}
}`
export default compose(
graphql(GET_AUTH_STATUS, {
props: ({ data: { loading, error, loggedInStatus } }) => {
if (loading) {
return { loading };
}
if (error) {
return { error };
}
return {
loading: false,
loggedInStatus
};
},
}),
graphql(UPDATE_LOGGED_IN_STATUS, {
props: ({ mutate }) => ({
updateLoggedInStatus: isLoggedIn => mutate({ variables: { isLoggedIn } }),
}),
}),
graphql(AUTHENTICATE, {
props: ({ mutate }) => ({
authenticate: (username, password) => mutate({ variables: { username, password } }),
}),
})
)(withRouter(Login));
So as you can see I have this.props.authenticate(username, password) which is used when the login form is submitted.
Then I have the this.props.updateLoggedInStatus(Boolean) which I am able to update the client cache / state.
How do I combine these so that I can call authenticate() and if it's successful, set the loggedInStatus and if it fails, set a hasErrored or errorMessage flag of sorts?
Thanks in advance.
EDIT:
I have attempted to handle updating the state within the callback of my mutation.
// Form submission handler
onSubmit = async ({ username, password }) => {
this.setState({loading: true})
this.props.authenticate(username, password)
.then(res => {
this.setState({loading: false})
this.props.updateLoggedInStatus(true)
})
.catch(err => {
this.setState({loading: false, errorMessage: err.message})
console.log('err', err)
})
}
Is there a better way of doing it than this? It feels very convoluted having to wait for the call back. I would have thought I could map the response to my cache object via my resolver?
I think the way you're currently handling it (calling authenticate and then updateLoggedInStatus) is about as clean and simple as you're going to get with apollo-link-state. However, using apollo-link-state for this is probably overkill in the first place. It would probably be simpler to derive logged-in status from Apollo's cache instead. For example, you could have a HOC like this:
import client from '../wherever/client'
const withLoggedInUser = (Component) => {
const user = client.readFragment({
id: 'loggedInUser',
fragment: gql`
fragment loggedInUser on User { # or whatever your type is called
username
sales_channel
full_name
roles
# be careful about what fields you list here -- even if the User
# is in the cache, missing fields will result in an error being thrown
}
`
})
const isLoggedIn = !!user
return (props) => <Component {...props} user={user} isLoggedIn={isLoggedIn}/>
}
Notice that I use loggedInUser as the key. That means we also have to utilize dataIdFromObject when configuring the InMemoryCache:
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory'
const cache = new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'User': return 'loggedInUser'
// other types you don't want the default behavior for
default: return defaultDataIdFromObject(object);
}
}
})