Handle firebase-authentication callback without firebaseui-web (next-firebase-auth) - reactjs

I have been dealing with a problem for several days and do not manage to solve it well.
I am using Firebase and NextJs in my current Project.
My current goal is to log in a user with firebase-authentication
.
However, it should also be possible to call the user in getServerSideProps and also to generate the IdToken within this.
The next-firebase-auth package makes life a lot easier. Basically, the package allows you to do just that.
When the user signs in, an endpoint '/api/login' is called to generate a refresh token and store the user info, ID token, and refresh token in cookies. Future requests to SSR pages receive the user info and ID token from cookies, refreshing the ID token as needed.
I am theoretically able to perform user.getIdToken() in getServerSideProps.
There is an example scenario within the repo, but which uses the
firebaseui-web. This enables you to make all the customizations you need.
Within this, inside the callback, signInSuccessWithAuthRes is set to false. And as I understand it, the key is here, as only then there is a redirection to '/api/login' to where the cookie is set.
However, I don't want to use the firebaseui-web as the UI looks bad.
I use the "normal way" of logging in my user with firebase.auth(), but I am not redirected to '/api/login'.
I tried to strive for the route myself, but always got an error
Error: The request is missing an Authorization header value
(I tried to set the Firebase IdToken as Bearer Authorization, but it did not work)
Is there a way to achieve this, without the firebaseui-web ?
My Approach
./pages/api/login
import { setAuthCookies } from 'next-firebase-auth'
import initAuth from '../../initAuth'
initAuth()
const handler = async (req, res) => {
try {
await setAuthCookies(req, res)
} catch (e) {
return res.status(500).json({ error: 'Unexpected error.' })
}
return res.status(200).json({ success: true })
}
export default handler
Part of my loginFuntion
const result = await firebase.auth().signInWithPopup(provider)
const token = await result.user.getIdToken(true)
await axios.post('/api/login', {
headers: {
Authorization: `Bearer ${token}`,
},
})
Example Code from next-firebase-auth GitHub Repo (FirebasAuth.js)
import React, { useEffect, useState } from 'react'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
import firebase from 'firebase/app'
import 'firebase/auth'
// Note that next-firebase-auth inits Firebase for us,
// so we don't need to.
const firebaseAuthConfig = {
signInFlow: 'popup',
// Auth providers
// https://github.com/firebase/firebaseui-web#configure-oauth-providers
signInOptions: [
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: false,
},
],
signInSuccessUrl: '/',
credentialHelper: 'none',
callbacks: {
// https://github.com/firebase/firebaseui-web#signinsuccesswithauthresultauthresult-redirecturl
signInSuccessWithAuthResult: () =>
// Don't automatically redirect. We handle redirects using
// `next-firebase-auth`.
**// Thats the Part I am not able to implement in my code !!!**
false,
},
}
const FirebaseAuth = () => {
// Do not SSR FirebaseUI, because it is not supported.
// https://github.com/firebase/firebaseui-web/issues/213
const [renderAuth, setRenderAuth] = useState(false)
useEffect(() => {
if (typeof window !== 'undefined') {
setRenderAuth(true)
}
}, [])
return (
<div>
{renderAuth ? (
<StyledFirebaseAuth
uiConfig={firebaseAuthConfig}
firebaseAuth={firebase.auth()}
/>
) : null}
</div>
)
}
export default FirebaseAuth
initAuth.js (if you need)
import { init } from 'next-firebase-auth'
const TWELVE_DAYS_IN_MS = 12 * 60 * 60 * 24 * 1000
const initAuth = () => {
init({
debug: true,
authPageURL: '/auth',
appPageURL: '/',
loginAPIEndpoint: '/api/login', // required
logoutAPIEndpoint: '/api/logout', // required
// firebaseAuthEmulatorHost: 'localhost:9099',
// Required in most cases.
firebaseAdminInitConfig: {
credential: {
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
// Using JSON to handle newline problems when storing the
// key as a secret in Vercel. See:
// https://github.com/vercel/vercel/issues/749#issuecomment-707515089
privateKey: process.env.FIREBASE_PRIVATE_KEY
? JSON.parse(process.env.FIREBASE_PRIVATE_KEY)
: undefined,
},
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
},
firebaseClientInitConfig: {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
},
cookies: {
name: 'ExampleApp',
keys: [
process.env.NEXT_COOKIE_SECRET_CURRENT,
process.env.NEXT_COOKIE_SECRET_PREVIOUS,
],
httpOnly: true,
maxAge: TWELVE_DAYS_IN_MS,
overwrite: true,
path: '/',
sameSite: 'strict',
secure: process.env.NEXT_PUBLIC_COOKIE_SECURE === 'true',
signed: true,
},
})
}

Related

How can I use Next.js redirects?

there!
I want to configure the first screen as a login page.
However, after logging in, we want to prevent the user from going to the login page after confirming the login with the cookie value.
The configuration file is as below, and how can I solve it?
next.config.js
module.exports = {
async redirects() {
return [
{
source: "/",
destination: "/login",
permanent: false,
has: [
{
type: "cookie",
key: "authorized",
value: "access_token",
},
],
},
];
},
};
This doesn't look possible to me, since in the config we only can have static values, and authtoken will change for every login, UI side redirection must be handled from separate AuthContext like we do with react apps.
Another alternative to above approach
is having one more cookie like 'authorized' and it will have value let say true of false. So we can check for 'authorized' is it has value 'true', next.config is below for the same.
Reference: https://nextjs.org/docs/api-reference/next.config.js/redirects#header-cookie-and-query-matching
{
reactStrictMode: true,
async redirects() {
return [
{
source: '/',
destination: '/login',
permanent: true,
has: [
{
type: 'cookie',
key: 'authorized',
value: 'false',
},
],
},
]
},
}
A more robust and controllable approach would be using something like nextAuth
You would have to follow two steps
In order to cover both server side and client-side scenarios (users logging directly on a logged in page, implement both these ) you can conditionally redirect using a router.push on client and getInitialProps with a 302 on the server side
i.e Using nextAuth
import { useSession, getSession } from "next-auth/react"
export default function Page() {
const { data: session, status } = useSession()
if (status === "loading") {
return <p>Loading...</p>
}
if (status === "unauthenticated") {
return <p>Access Denied</p>
}
// Do a router.push here
}
Serverside
import { useSession, getSession } from "next-auth/react"
export default function Page() {
const { data: session } = useSession()
if (typeof window === "undefined") return null
if (session) {
// do a 302 redirect, using ctx.resShead via getInitialprops
return <p>Access Denied</p>
}
export async function getServerSideProps(context) {
return {
props: {
session: await getSession(context),
},
}
}
In order for nextAuth to get cookies, declare it as a provider
see example here - https://stackoverflow.com/a/69418553/13749957

How to get session in NextJS api route using with-iron-session?

I am using with-iron-session for authentication in my NextJS app however I'm not able to access the session cookie when I make API calls in my getServerSideProps() function. The API route is unable to get the session cookie used for authentication:
Session creation:
// this file is a wrapper with defaults to be used in both API routes and `getServerSideProps` functions
import { withIronSession } from "next-iron-session";
export default function withSession(handler) {
return withIronSession(handler, {
password: process.env.PASSWORD_HASH,
cookieName: "MYCOOKIE",
cookieOptions: {
// the next line allows to use the session in non-https environements like
// Next.js dev mode (http://localhost:3000)
secure: process.env.NODE_ENV === "production",
httpOnly: false,
},
});
}
My getServerSideProps call:
export const getServerSideProps = withSession(async ({ req, res }) => {
const user = req.session.get("user");
if (!user) {
return {
redirect: {
permanent: false,
destination: "/"
},
props: {}
};
}
// I've replaced 'include' with 'same-origin' but it didn't make a difference
const watchRes = await fetch('/watch',{credentials: 'include'});
const watch = await watchRes.json();
return{
props: {
user,
watch
}
}
}
the api route:
// I've added await before the (req,res) but that was just guessing at this point
export default withSession((req, res) => {
const user = req.session.get("user");
if(user){
res.send("Good");
}else{
res.status(403).end();
}
}
While being logged in and I go to my localhost/api/watch route, it shows "Good" but when I try the fetch request I get the 403.
try to add headers of the getServerSideProps request to the second call.
const headers = req.headers
const watchRes = await fetch('/watch',{headers});
in this way you have injected the cookies

react-aad-msal authProvider.getAccessToken() reloads the component indefinitely

I have added a axios interceptor within which authProvider.getAccessToken() is called to fetch token and add to header of each request.
Here is my axiosInterceptor.js
import axios from 'axios'
import { authProvider } from '../authProvider'
export const axiosApiIntance = axios.create()
export const axiosInterceptor = axiosApiIntance.interceptors.request.use(async request => {
try {
let token = await authProvider.getAccessToken()
request.headers['Authorization'] = `Bearer ${token.accessToken}`
return request
} catch (err) {
console.log(err)
}
}, error => {
return Promise.reject(error.message)
})
Here is my authProvider.js
import { LoginType, MsalAuthProvider } from 'react-aad-msal'
// The auth provider should be a singleton. Best practice is to only have it ever instantiated once.
// Avoid creating an instance inside the component it will be recreated on each render.
// If two providers are created on the same page it will cause authentication errors.
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: process.env.REACT_APP_AUTHORITY,
clientId: process.env.REACT_APP_CLIENT_ID,
postLogoutRedirectUri: process.env.REACT_APP_URL,
redirectUri: process.env.REACT_APP_URL,
validateAuthority: true,
// After being redirected to the "redirectUri" page, should user
// be redirected back to the Url where their login originated from?
navigateToLoginRequestUrl: false
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true
}
},
{
scopes: ['openid', 'profile', 'user.read']
},
{
loginType: LoginType.Redirect,
// When a token is refreshed it will be done by loading a page in an iframe.
// Rather than reloading the same page, we can point to an empty html file which will prevent
// site resources from being loaded twice.
tokenRefreshUri: window.location.origin + '/auth.html'
}
)
authProvider is used in App.js
<AzureAD provider={authProvider} reduxStore={configureStore}>
....
</AzureAD>
axiosInterceptor is also included in App.js.
Please provide suggestion on what could cause the component the reload indifinitely.
I have removed the authProvider.getAccessToken() and verified, it works fine. So the reload is caused due to that.
First, I suggest you to verify the Scope, authority and clientId of your AuthProvider.
I had a similar issue in one project ans I had to add the scope to the getAccessToken() function, even if I never did that in others projects..
See below:
var authenticationParameters = {
scopes: ['openid', 'profile', 'user.read'],
};
axios.interceptors.request.use(function (config): any {
return new Promise(async (resolve: any, reject: any) => {
await authProvider.getAccessToken(authenticationParameters).then((response: any) => {
config.headers["Authorization"] = "Bearer " + response.accessToken;
config.headers["Content-Type"] = "application/json";
config.headers.Accept = "application/json";
resolve(config);
})
.catch((error: any) => {
console.log(error.message);
});
});
});
Hope it help ;)
Regards

Laravel lighthouse current user is null via next apollo

I have a fresh copy of laravel with sanctum and lighthouse. When I do the login route via axios, everything works as expected. After logging in via axios, I added a lazyquery to attempt to query some guarded fields but I get unauthenticated. I am not sure why and it has been three days I've been dealing with this. I'd really appreciate your help.
This works
useEffect(() => {
axios.defaults.withCredentials = true;
// get the token from the server
axios.get(`http://api.newods.test/sanctum/csrf-cookie`).then(function (resolve){
// try login with the user
axios.post('http://api.newods.test/api/login', {
email: 'test#test.com',
password: 'test'
}).then(function (resolve) {
console.log(`logged in ${resolve.data}`);
axios
.get("http://api.newods.test/api/gated", { withCredentials: true })
.then(function (resolve) {
console.log(`gated ${resolve.data}`);
axios
.get("http://api.newods.test/api/logout", {
withCredentials: true,
})
.then(function (resolve) {
console.log(`logged out ${resolve.data}`);
axios
.get("http://api.newods.test/api/gated", {
withCredentials: true,
})
.then(function (resolve) {
console.log(
`trying to get to gated after logging out ${resolve.data}`
);
});
});
});
});
});
}, []);
But when I cut it short and change to this, I get unauthenticated
const HELLO = gql\`
query hello {
hello
}
`;
function Home() {
const [hello, { loading, data }] = useLazyQuery(HELLO);
useEffect(() => {
axios.defaults.withCredentials = true;
// get the token from the server
axios.get(`http://api.newods.test/sanctum/csrf-cookie`).then(function (resolve){
// try login with the user
axios.post('http://api.newods.test/api/login', {
email: 'test#test.com',
password: 'test'
}).then(function (resolve) {
console.log('logged in');
});
});
}, []);
return (
<div className="container">
<div>Index</div>
<button onClick={() => hello()}>
Click to hello world
</button>
<p>{data && data.hello || ''}</p>
</div>
);
}
export default withApollo(Home);
And that returns unauthenticated when I add the #guard directive and I see the token from the axios login request is in the headers... I am not sure what I am missing here I'd greatly appreciate your help.
schema.graphql
type Query {
users: [User!]! #paginate(defaultCount: 10)
user(id: ID #eq): User #find
hello: String! #guard
me: User #auth
}
.env
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_DOMAIN=.newods.test
SANCTUM_STATEFUL_DOMAINS=newods.test:3000
config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie', 'graphql'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
config/lighthouse
'route' => [
/*
* The URI the endpoint responds to, e.g. mydomain.com/graphql.
*/
'uri' => '/graphql',
/*
* Lighthouse creates a named route for convenient URL generation and redirects.
*/
'name' => 'graphql',
/*
* Beware that middleware defined here runs before the GraphQL execution phase,
* make sure to return spec-compliant responses in case an error is thrown.
*/
'middleware' => [
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
// Logs in a user if they are authenticated. In contrast to Laravel's 'auth'
// middleware, this delegates auth and permission checks to the field level.
\Nuwave\Lighthouse\Support\Http\Middleware\AttemptAuthentication::class,
],
/*
* The `prefix` and `domain` configuration options are optional.
*/
//'prefix' => '',
//'domain' => '',
],
In my next app with apollo
create.js
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import Cookies from 'js-cookie';
import { serverUrl } from '../config';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = Cookies.get("XSRF-TOKEN");
// console.log(`token is ${token}`);
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
"Access-Control-Allow-Credentials": true,
...(token ? { authorization: `X-XSRF-TOKEN=${token}` } : {}),
},
};
});
const httpLink = createHttpLink({
uri: serverUrl,
credentials: 'same-origin',
});
return new ApolloClient({
ssrMode: Boolean(ctx),
link: authLink.concat(httpLink),
connectToDevTools: true,
cache: new InMemoryCache().restore(initialState),
});
}
withApollo.js
import React from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
import createApolloClient from './create';
let apolloClient = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* #param {Function|Class} PageComponent
* #param {Object} [config]
* #param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
PageComponent.displayName || PageComponent.name || "Component";
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.");
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient(
{},
ctx.req.headers.cookie
));
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("#apollo/react-ssr");
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
// #ts-ignore
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* #param {Object} initialState
*/
function initApolloClient(initialState = {}, cookie = "") {
// Make sure to create a new client for every server-side request so that data
// isn"t shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, cookie);
}
// Reuse client on the client-side
if (!apolloClient) {
// #ts-ignore
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
I have a very similar architecture, but using Vue. From comparing your code against my working implementation, I think the majority of your problems are in create.js.
I don't know much about js-cookie, but this is how I get the XSRF-TOKEN, and decode it.
let token = RegExp('XSRF-TOKEN[^;]+').exec(document.cookie)
token = decodeURIComponent(token ? token.toString().replace(/^[^=]+./, '') : '')
Then, in your setContext, you need to set the header as follows.
return {
headers: {
...headers,
'X-XSRF-TOKEN': token,
}
}
Also, I had trouble with credentials: 'same-origin' even though I'm using a subdomain. Therefore I would suggest:
const httpLink = createHttpLink({
uri: serverUrl,
credentials: 'include',
})

'AADSTS500011' error message returned from API call using adalFetch

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.

Resources