I am creating a React calendar that take data from "Microsoft Outlook Calendar" using the client-side JavaScript SDK "hello.js" and Microsoft Graph (for the set up I also followed this guide: https://learn.microsoft.com/en-us/graph/auth-register-app-v2).
Using hello.login my app shows the calendar without any problem...but unfortunately I have to show it without a login session.
This is my code:
class CalendarView extends Component {
constructor(props) {
super(props);
hello.init({
microsoft: {
id: APP_ID,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
},
scope_delim: ' ',
form: false,
scope: SCOPES,
},
});
const { startDate, endDate } = this.props;
this.state = {
// events: [],
startDataTime: startDate.toISOString(),
endDataTime: endDate.toISOString(),
token: hello('microsoft').getAuthResponse().access_token,
};
}
In this other component I mange the Microsoft Graph Query:
class EventsList extends Component {
constructor() {
super();
this.state = {
events: [],
};
}
componentWillReceiveProps(nextProps) {
const { startDate, endDate, token } = nextProps;
// to know what is the Bearer toke
// -> https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly
axios.get(
`https://graph.microsoft.com/v1.0/me/calendarview?startdatetime=${startDate}&enddatetime=${endDate}&orderby=start/dateTime`,
{ headers: { Authorization: `Bearer ${token}` } },
).then(response => this.setState({ events: response.data.value }))
.catch((error) => {
console.log(error.response);
});
}
render() {
const { events } = this.state;
if (events !== null) return events.map(event => <EventList key={event.id} event={event} />);
return null;
}
}
The strange thing is that if I make a console.log(token) the app show me the token but, at the same time, I receive an "GET...401 (Unauthorized)" error
console log token and error message
That are my app propriety:
app propriety part 1
app propriety part 2
Maybe the problem is the Hello.js call?
I am testing my app with Jest and I have this error, can it be linked to my problem?
console.error node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/virtual-console.js:29
Error: Uncaught [TypeError: hello is not a function]
How Can I solve?
I found the solution!
I had to make 2 axios call:
one to obtain the token (with a POST)
one for use the token in my microsoft graph query (with a GET)
I had to register my app here https://portal.azure.com/#home so to obtain a Client ID and Secret.
After I needed to send a POST message to Azure Active Directory Authentication endpoint with following body parameters:
grant_type: The flow we want to use, client_credentials in my case.
client_id: The Client ID (Application ID) of the application I
created in the registration step;
client_secret: The Client Secret I created in the registration
step;
resource: The name of the resource I would like to get access,
https://graph.microsoft.com in this case.
So I created one component with the following axios POST request:
componentDidMount() {
axios.post(`https://cors-anywhere.herokuapp.com/https://login.microsoftonline.com/${AZURE_ACTIVE_DIRECTORY_TENANT_NAME}/oauth2/token`,
`grant_type=${GRANT_TYPE}&client_id=${APP_ID}&client_secret=${SECRET}&resource=${RESOURCE}`).then(res => this.setAccessToken(res.data.access_token))
.catch((error) => {
console.error(error.response);
});
}
setAccessToken(token) {
if (typeof token === 'string') this.setState({ accessToken: token });
}
NOTE
the resource value needed to be a bit changed to work:
https%3A%2F%2Fgraph.microsoft.com%2F
I had to put the string 'https://cors-anywhere.herokuapp.com' before micorsoftonline URL because otherwise the application generated
"a blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
(I don't know why, I am still working on it because putting this string before is not an optimal solution).
In EventList component I didn't need hellojs anymore, so I just use the token I generated to access. I had to change just a bit the microsoft graph query:
componentDidMount() {
const { accessToken } = this.props;
const { startDate, endDate } = this.state;
this.getEvents(startDate, endDate, accessToken);
}
getEvents(startDate, endDate, accessToken) {
const startDateString = startDate.toISOString();
const endDateString = endDate.toISOString();
axios.get(
`https://graph.microsoft.com/v1.0/users/${USER_PUBLIC_ID}/calendarview?startdatetime=${startDateString}&enddatetime=${endDateString}&orderby=start/dateTime`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
).then(response => this.setEvents(response.data.value))
.catch((error) => {
console.error(error.response);
});
}
setEvents(events) {
const validEvent = event => typeof event.subject === 'string';
this.setState({ events: events.filter(validEvent) });
}
I hope that my solution can be usefull also to other users
Related
How do you implement payments in React Native 0.70. I worked with earlier React Native versions using react-native-credit-card-input and react-native-credit-card-input-plus that are now breaking.
Now it very easy to implement the payment methods in react-native because stripe provide official doc.
They provide a built-in UI for checkout and Card Tokenisation,
Here you can Follow Official Doc
1) Setup
install stripe official react-native sdk
yarn add #stripe/stripe-react-native
To initialise Stripe in your React Native app, either wrap your payment screen with the StripeProvider component, or use the initStripe initialisation method.
<StripeProvider publishableKey={PUBLISHABLE_KEY}>
<Navigation />
</StripeProvider>
How to get PUBLISHABLE_KEY
Now in your component
Either use the Stripe UI or create your own custom UI for getting card details. In this answer, I'm using rn-credit-card for getting a card, which gives me customization options 🙂.
2) Get Card details, create Card Token and save for future use
import CreditCardForm, { FormModel } from "rn-credit-card";
const handleConfirm = (model: FormModel) => {
axios
.post(
"https://api.stripe.com/v1/tokens",
{
"card[name]": model.holderName,
"card[number]": model.cardNumber,
"card[exp_month]": model.expiration.split("/")[0],
"card[exp_year]": model.expiration.split("/")[1],
"card[cvc]": model.cvv,
},
{
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${STRIPE_KEY}`,
},
}
)
.then((res) => {
if (res?.data?.id) {
//res?.data?.id It will return the payment method ID sent to your backend
// You can also save it for future use by saving it in the database.
console.log(res?.data?.id)
}
})
.catch((err) => {
Alert.alert("Stripe Error", err.message);
});
};
For setting defaultValues
const formMethods = useForm<FormModel>({
mode: "onBlur",
defaultValues: {
holderName: "",
cardNumber: "",
expiration: "",
cvv: "",
},
});
const { handleSubmit, formState } = formMethods;
Form to get card details
<CreditCardForm
LottieView={LottieView}
horizontalStart={false}
overrides={{
labelText: {
marginTop: 16,
},
}}
/>
{formState.isValid && (
<Button
style={styles.button}
title={'CONFIRM PAYMENT'}
onPress={handleSubmit(handleConfirm)}
/>
)}
Now When you pay or checkout simple do the following step
3) Checkout or Payment Time
Create a PaymentIntent by passing the paymentMethods Id with other params like reservationId etc
The backend will return you clientSecret and also the calculated bill
Send the clientSecret to stripe
import { useStripe } from "#stripe/stripe-react-native";
const { confirmPayment } = useStripe();
const handlePay = async () => {
setStripeLoading(true);
try {
//Step 1
const response = await createPaymentIntent({
variables: {
paymentMethodId: paymentMethodId, // That you stored on the backend
reserveId: id, // In our case reserveId is required
amountToDeduct: 23,
},
});
if (response) {
//Step 2 by getting clientSecret
const { clientSecret } = response?.createPaymentIntent;
//sending clientSecret to deduct amount
const { error, paymentIntent } = await confirmPayment(clientSecret);
if (error) {
setStripeLoading(false);
Alert.alert(`Error code: ${error.code}`, error.message);
}
if (paymentIntent) {
setStripeLoading(false);
// Show Success Alert
}
}
} catch (error) {
setStripeLoading(false);
} finally {
setStripeLoading(false);
}
};
Tada you done 🥰
In my app, I have an access token (Spotify's) which must be valid at all times. When this access token expires, the app must hit a refresh token endpoint, and fetch another access token, every 60 minutes.
Authorize functions
For security reasons, these 2 calls, to /get_token and /refresh_token are dealt with python, server-side, and states are currently being handled at my Parent App.jsx, like so:
class App extends Component {
constructor() {
super();
this.state = {
users: [],
isAuthenticated: false,
isAuthorizedWithSpotify: false,
spotifyToken: '',
isTokenExpired:false,
isTokenRefreshed:false,
renewing: false,
id: '',
};
componentDidMount() {
this.userId(); //<--- this.getSpotifyToken() is called here, inside then(), after async call;
};
getSpotifyToken(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/get_token/${this.state.id}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`,
}
};
// needed for sending cookies
axios.defaults.withCredentials = true
return axios(options)
.then((res) => {
console.log(res.data)
this.setState({
spotifyToken: res.data.access_token,
isTokenExpired: res.data.token_expired // <--- jwt returns expiration from server
})
// if token has expired, refresh it
if (this.state.isTokenExpired === true){
console.log('Access token was refreshed')
this.refreshSpotifyToken();
}
})
.catch((error) => { console.log(error); });
};
refreshSpotifyToken(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/refresh_token/${this.state.id}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`,
}
};
axios.defaults.withCredentials = true
return axios(options)
.then((res) => {
console.log(res.data)
this.setState({
spotifyToken: res.data.access_token,
isTokenRefreshed: res.data.token_refreshed,
isTokenExpired: false,
isAuthorizedWithSpotify: true
})
})
.catch((error) => { console.log(error); });
};
Then, I pass this.props.spotifyToken to all my Child components, where requests are made with the access token, and it all works fine.
Watcher Function
The problem is that, when the app stays idle on a given page for more than 60 minutes and the user makes a request, this will find the access token expired, and its state will not be updated, so the request will be denied.
In order to solve this, I thought about having, at App.jsx, a watcher function tracking token expiration time on the background, like so:
willTokenExpire = () => {
const accessToken = this.state.spotifyToken;
console.log('access_token in willTokenExpire', accessToken)
const expirationTime = 3600
const token = { accessToken, expirationTime } // { accessToken, expirationTime }
const threshold = 300 // 300s = 5 minute threshold for token expiration
const hasToken = token && token.spotifyToken
const now = (Date.now() / 1000) + threshold
console.log('NOW', now)
if(now > token.expirationTime){this.getSpotifyToken();}
return !hasToken || (now > token.expirationTime)
}
handleCheckToken = () => {
if (this.willTokenExpire()) {
this.setState({ renewing: true })
}
}
and:
shouldComponentUpdate(nextProps, nextState) {
return this.state.renewing !== nextState.renewing
}
componentDidMount() {
this.userId();
this.timeInterval = setInterval(this.handleCheckToken, 20000)
};
Child component
Then, from render() in Parent App.jsx, I would pass handleCheckToken() as a callback function, as well as this.props.spotifyToken, to the child component which might be idle, like so:
<Route exact path='/tracks' render={() => (
<Track
isAuthenticated={this.state.isAuthenticated}
isAuthorizedWithSpotify={this.state.isAuthorizedWithSpotify}
spotifyToken={this.state.spotifyToken}
handleCheckToken={this.handleCheckToken}
userId={this.state.id}
/>
)} />
and in the Child component, I would have:
class Tracks extends Component{
constructor (props) {
super(props);
this.state = {
playlist:[],
youtube_urls:[],
artists:[],
titles:[],
spotifyToken: this.props.spotifyToken
};
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.props.handleCheckToken();
};
};
and a call where the valid, updated spotifyToken is needed, like so:
getTrack(event) {
const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/get-tracks/${userId}/${this.props.spotifyToken}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.message)
})
.catch((error) => { console.log(error); });
};
But this is not working.
At regular intervals, new tokens are being fetched with handleCheckToken, even if I'm idle at Child. But if I make the request after 60 minutes, in Child, this.props.spotifyToken being passed is expired, so props is not being passed down to Child.jsx correctly.
What am I missing?
You are talking about exchanging refreshToken to accessToken mechanism and I think that you over complicated it.
A background, I've a similar setup, login generates an accessToken (valid for 10 mins) and a refreshToken as a cookie on the refreshToken end point (not necessary).
Then all my components are using a simple api service (which is a wrapper around Axios) in order to make ajax requests to the server.
All of my end points are expecting to get a valid accessToken, if it expired, they returns 401 with an expiration message.
My Axios has a response interceptor which check if the response is with status 401 & the special message, if so, it makes a request to the refreshToken endpoint, if the refreshToken is valid (expires after 12 hours) it returns an accessToken, otherwise returns 403.
The interceptor gets the new accessToken and retries (max 3 times) the previous failed request.
The cool think is that in this way, accessToken can be saved in memory (not localStorage, since it is exposed to XSS attack). I save it on my api service, so, no Component handles anything related to tokens at all.
The other cool think is that it is valid for a full page reload as well, because if the user has a valid cookie with a refreshToken, the first api will fail with 401, and the entire mechanism will work, otherwise, it will fail.
// ApiService.js
import Axios from 'axios';
class ApiService {
constructor() {
this.axios = Axios.create();
this.axios.interceptors.response.use(null, this.authInterceptor);
this.get = this.axios.get.bind(this.axios);
this.post = this.axios.post.bind(this.axios);
}
async login(username, password) {
const { accessToken } = await this.axios.post('/api/login', {
username,
password,
});
this.setAccessToken(accessToken);
return accessToken; // return it to the component that invoked it to store in some state
}
async getTrack(userId, spotifyToken) {
return this.axios.get(
`${process.env.REACT_APP_WEB_SERVICE_URL}/get-tracks/${userId}/${spotifyToken}`
);
}
async updateAccessToken() {
const { accessToken } = await this.axios.post(`/api/auth/refresh-token`, {});
this.setAccessToken(accessToken);
}
async authInterceptor(error) {
error.config.retries = error.config.retries || {
count: 0,
};
if (this.isUnAuthorizedError(error) && this.shouldRetry(error.config)) {
await this.updateAccessToken(); // refresh the access token
error.config.retries.count += 1;
return this.axios.rawRequest(error.config); // if succeed re-fetch the original request with the updated accessToken
}
return Promise.reject(error);
}
isUnAuthorizedError(error) {
return error.config && error.response && error.response.status === 401;
}
shouldRetry(config) {
return config.retries.count < 3;
}
setAccessToken(accessToken) {
this.axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`; // assign all requests to use new accessToken
}
}
export const apiService = new ApiService(); // this is a single instance of the service, each import of this file will get it
This mechanism is based on this article
Now with this ApiService you can create a single instance and import it in each Component that whats to make an api request.
import {apiService} from '../ApiService';
class Tracks extends React.Component {
constructor(props) {
super(props);
this.state = {
playlist: [],
youtube_urls: [],
artists: [],
titles: [],
spotifyToken: this.props.spotifyToken,
};
}
async componentDidMount() {
if (this.props.isAuthenticated) {
const {userId, spotifyToken} = this.props;
const tracks = await apiService.getTracks(userId, spotifyToken);
this.setState({tracks});
} else {
this.setState({tracks: []});
}
}
render() {
return null;
}
}
Edit (answers to comments)
Handling of login flow can be done using this service as well, you can extract the accessToken from the login api, set it as a default header and return it to the caller (which may save it in a state for other component logic such as conditional rendering)(updated my snippet).
it is just an example of component which needs to use api.
there is only one instance of the ApiService it is created in the "module" of the file (at the end you can see the new ApiService), after that you just importing this exported instance to all the places that need to make an api call.
If you want to force a rerender of your Child component when the state of the parent component changes, you can use the key prop. Use the spotify token as the key. When the spotify token is re-fetched and updated, it will remount your child component with the new token as well:
<Route exact path='/child' render={() => (
<Child
isAuthenticated={this.state.isAuthenticated}
isAuthorizedWithSpotify={this.state.isAuthorizedWithSpotify}
spotifyToken={this.state.spotifyToken}
key={this.state.spotifyToken}
handleCheckToken={this.handleCheckToken}
userId={this.state.id}
/>
)} />
Though this may reset any internal state that you had in your child component, as it is essentially unmounting and remounting the Child.
Edit: More details
The key prop is a special prop used in React components. React uses keys to determine whether or not a component is unique, from one component to another, or from one render to another. They are typically used when mapping an array to a set of components, but can be used in this context as well. The react docs have an excellent explanation. Basically when the key prop of a component changes, it tells React that this is now a new component, and therefore must be completely rerendered. So when you fetch a new spotifyToken, and assign that new token as the key, React will completely remount the Child with the new props. Hopefully that makes things more clear.
The key prop will not be available from within your Child - this.props.key inside of your child will not be useful to try to access. But in your case, you are passing the same value to the spotifyToken inside the Child, so you'll have the value available there. Its common to use another prop with the same value as key when that value is needed in the child component.
props will not updates on the child, because for a child component they are like immutable options: https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
So you will need some ways to re-render the Child component.
The Child component has already been constructed so will not update and re-render.
So you will need to use "getDerivedStateFromProps()" as a replacement from the deprecated "componentWillReceiveProps" function inside the Child component, so that when receiving updated props from the parent the child will re-render, like this:
class Child extends Component {
state = {
spotifyToken: null,
};
static getDerivedStateFromProps(props, state) {
console.log("updated props", props.spotifyToken);
if (props.spotifyToken !== state.spotifyToken) {
return {
spotifyToken: props.spotifyToken,
};
}
// Return null if the state hasn't changed
return null;
}
getTrack(event) {
const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/get-tracks/${userId}/${this.state.spotifyToken}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.message)
console.log(options.url)
})
.catch((error) => { console.log(error); });
}
};
Note that in the getTrack function you will use the Child state value and not the props.
I am fetching a profile object from my API following user authentication. The fetch returns the profile object as expected, however my server logger clearly shows a profile object containing an "id" and "username", but the initial object returned to the client has only the "username". I am only able to access the "id" property of the profile abject after I refresh.
Not sure how to fix this, but ive tried everything I can think of...
Login Form
export default class LoginForm extends Component {
static defaultProps = {
onLoginSuccess: () => { }
}
state = { error: null }
handleSubmitJwtAuth = ev => {
ev.preventDefault()
this.setState({ error: null })
const { username, password } = ev.target
//login request
AuthApiService.postLogin({
username: username.value,
password: password.value,
})
//login response
.then(res => {
//updates context profile with username value after login
this.props.updater({ username: username.value })
username.value = ''
password.value = ''
TokenService.saveAuthToken(res.authToken)
this.props.onLoginSuccess()
})
.catch(res => {
this.setState({ error: res.error })
})
}
Profile API Service
const ProfileApiService = {
getProfile() {
return fetch(`${config.API_ENDPOINT}/profile`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `bearer ${TokenService.getAuthToken()}`
}
})
.then(res => {
return (!res.ok)
? res.json().then(e => Promise.reject(e))
: res.json()
}
);
}
}
(API) Profile Service
const ProfileService = {
getProfile : (db,id) =>{
return db
.from('v_users')
.select('id','username')
.where({id})
.first();
},
serializeProfile(profile){
return {
id: profile.id,
username: xss(profile.username)
};
}
}
initially, console.log(this.state.profile.id) //undefined
after a refresh, console.log(this.state.profile.id) // 7
the server log shows this object being returned initially
{ id: 7, username: 'qber83' }, however as mentioned above, I am unable to access the "id" property without refreshing the browser.
The problem here could be that your state is not updated properly, since the object returned is right the API services work, so here your context updater or this.props.onLoginSuccess() might contain the issue.
I have a React application that is registered in Azure Active Directory. In the API Permissions section, I have added permissions to access the API I am trying to access.
I am using the react-adal package to handle login and storage of access tokens when the user enters the app. My understanding is that the access token for the API is created at this point and adalFetch handles the logistics during the call to the API.
The response from the API is an error object (I replaced the actual id's; yes they match exactly and are correct in AAD):
{
message: "AADSTS500011: The resource principal named https://<domain>.onmicrosoft.com/APP_ID/access_as_user was not found in the tenant named TENANT. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant."
msg: "invalid_resource"
}
I have searched high and low to find a solution to why this isn't working. There is documentation on the API, but none specifying a resource or anything beyond the various endpoints i.e. http://thing-api.azurewebsites.net/api/endpointGoesHere
The API page states:
To use the API, apps need to implement modern authentication (OIDC) using AzureAD (AAD) and then request a token from AAD for the API.
The app id in Azure is https://domain.onmicrosoft.com/APP_ID and requires the “access_as_user” scope.
adalConfig.js
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
clientId: CLIENT_ID,
tenant: TENANT,
endpoints: {
thingApi: 'https://<domain>.onmicrosoft.com/APP_ID/access_as_user',
graphApi: 'https://graph.microsoft.com',
},
cacheLocation: 'localStorage',
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.thingApi, fetch, url, options);
export const adalGraphFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.graphApi, fetch, url, options);
Function for the API call. Executed in componentDidMount.
TrainLanding.jsx
//Returns error
fetchData = () => {
adalApiFetch(fetch, 'http://thing-api.azurewebsites.net/api/EventGet', {})
.then((response) => {
response.json()
.then((responseJson) => {
this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) }, () => {
console.log(this.state.apiResponse)
})
});
})
.catch((error) => {
console.error(error);
})
}
//works perfectly fine
fetchGraph = () => {
adalGraphFetch(fetch, 'https://graph.microsoft.com/v1.0/me', {})
.then((response) => {
response.json()
.then((responseJson) => {
this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) }, () => {
console.log(this.state.apiResponse)
})
});
})
.catch((error) => {
console.error(error);
})
}
I set up a graph API call in the exact same way to test the method, and it works perfectly fine. So I know adal is set up correctly, I just don't understand the error and where I am going wrong. My googling has not yielded any useful results.
Ok, so if you're here, some things to note:
Don't use ADAL. Use MSAL. ADAL is v1 and does not work. Read here for examples: https://www.npmjs.com/package/react-aad-msal
You should wrap your entire app inside the component you get from above. I will show how I did it below.
You must have already registered your app in Azure Active Directory, configured redirect URLs, and included API permissions.
index.js
import { AzureAD, MsalAuthProviderFactory, LoginType } from 'react-aad-msal';
import { msalConfig, authParams } from './msalConfig';
class Index extends Component {
state = {
userInfo: null,
}
userJustLoggedIn = (accInfo) => {
this.setState({
userInfo: accInfo
})
}
render() {
return(
<AzureAD
provider={
new MsalAuthProviderFactory(msalConfig, authParams, LoginType.Redirect)
}
forceLogin={true}
accountInfoCallback={this.userJustLoggedIn}
>
<HashRouter>
<App userInfo={this.state.userInfo}/>
</HashRouter>
</AzureAD>
);
}
}
ReactDOM.render(
<Index/>, document.getElementById('root')
);
This might not be what your index looks like if you are using the most recent version of Create React App. I converted the Index into a component for a couple of reasons. First, the authentication loop for me was getting stuck 1 refresh short when redirecting. Second, so I could store the logged in user's info in state, update with setState (which forces another render), and then pass it as a prop to the rest of my app.
msalConfig.js
export const msalConfig = {
auth: {
authority: process.env.REACT_APP_AUTHORITY, //this should be "https://login.microsoftonline.com/<your-tenant-id>"
clientId: process.env.REACT_APP_CLIENT_ID, //just "<your-client-id>"
redirectUri: process.env.REACT_APP_REDIRECT //"<url of your app or localhost port you dev on>"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
export const authParams = {
//can be whatever api scopes you need here **as long as they are from the same API address**
scopes: [
'https://graph.microsoft.com/User.ReadBasic.All',
'https://graph.microsoft.com/email',
'https://graph.microsoft.com/profile',
'https://graph.microsoft.com/User.Read'
],
extraScopesToConsent: [
//any non Microsoft Graph API scopes go here for this example
'any extra strings of APIs to consent to'
]
}
Read above env files and variables here: https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#what-other-env-files-can-be-used
I have a .env.development and a .env.production with the proper redirect URLs for each.
After you have authenticated the user, you can access the API.
You need to acquire a token silently before each API call and use the token in the request. For me it looks like this:
const authProvider = new MsalAuthProviderFactory(msalConfig, authParams);
console.log(authProvider)
authProvider.getAuthProvider().UserAgentApplication.acquireTokenSilent(authParams)
.then((res) => {
axios({
headers: {
'Authorization': 'Bearer ' + res.accessToken
},
method: 'GET',
url: "api address"
})
.then((response) => {
//do stuff with response
console.log(response)
})
.catch((error) => {
console.log('axios fail: ' + error)
})
})
.catch((error) => {
console.log('token fail: ' + error)
})
I put this into a function and called during componentDidMount.
I will update if anything changes. I hope this helps someone.
I am trying to use Socket.io in React with an Express Backend, which using JWT (Passport JWT).
In my usual routes I have no problems with my Auth.
The Authorization header is sent (Authorization Bearer Token) and checked on the backend, however I simply do not get how to include the Bearer Token when sending data from React Client.
I have looked up the extraHeaders option but simply could not get them to work.
Here is my Code:
class Chat extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
message: "",
messages: []
};
this.socket = io("localhost:5000", {
extraHeaders: {
Authorization: "My Bearer authorization_token_here"
}
});
this.socket.on("RECEIVE_MESSAGE", function(data) {
addMessage(data);
});
const addMessage = data => {
console.log(data);
this.setState({ messages: [...this.state.messages, data] });
console.log(this.state.messages);
};
this.sendMessage = ev => {
ev.preventDefault();
this.socket.emit("SEND_MESSAGE", {
author: this.state.username,
message: this.state.message
});
this.setState({ message: "" });
};
}
render() {jsx code here}
The socket connection is set up, however I have no idea how to get the token to the server. Ideally I'd send a ID in the token, decode it on the server and lookup more info about the user in my DB.
Happy Holidays and I'm thankfull for any help or hints how to solve my problem.
As per, the documentation extraHeaders will only work in browsers if transport option is set to polling.
so enable polling on serverside and use the following snippet
io({
transportOptions: {
polling: {
extraHeaders: { "Authorization": "My Bearer authorization_token_here" }
},
}
});