Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46,275 changes: 30,435 additions & 15,840 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 30 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
{
"name": "react-with-aad",
"name": "ms-identity-javascript-react-spa",
"version": "0.1.0",
"private": true,
"dependencies": {
"@azure/msal-browser": "^2.13.1",
"@azure/msal-react": "^1.0.0-beta.1",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.1"
"@azure/msal-browser": "^2.32.2",
"@azure/msal-common": "^9.1.1",
"@azure/msal-react": "^1.5.2",
"bootstrap": "^5.2.3",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-dom": "^18.2.0",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
70 changes: 70 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import './styles/App.css';
import { PageLayout } from './components/PageLayout';
import { AuthenticatedTemplate, UnauthenticatedTemplate, useMsal } from '@azure/msal-react';
import Button from 'react-bootstrap/Button';
import { loginRequest } from './authConfig';
import { callMsGraph } from './graph';

import { ProfileData } from './components/ProfileData';


/**
* Renders information about the signed-in user or a button to retrieve data about the user
*/

const ProfileContent = () => {
const { instance, accounts } = useMsal();
const [graphData, setGraphData] = useState(null);

function RequestProfileData() {
// Silently acquires an access token which is then attached to a request for MS Graph data
instance
.acquireTokenSilent({
...loginRequest,
account: accounts[0],
})
.then((response) => {
callMsGraph(response.accessToken).then((response) => setGraphData(response));
});
}

return (
<>
<h5 className="profileContent">Welcome {accounts[0].name}</h5>
{graphData ? (
<ProfileData graphData={graphData} />
) : (
<Button variant="secondary" onClick={RequestProfileData}>
Request Profile
</Button>
)}
</>
);
};


/**
* If a user is authenticated the ProfileContent component above is rendered. Otherwise a message indicating a user is not authenticated is rendered.
*/
const MainContent = () => {
return (
<div className="App">
<AuthenticatedTemplate>
<ProfileContent />
</AuthenticatedTemplate>

<UnauthenticatedTemplate>
<h5 className="card-title">Please sign-in to see your profile information.</h5>
</UnauthenticatedTemplate>
</div>
);
};

export default function App() {
return (
<PageLayout>
<MainContent />
</PageLayout>
);
}
67 changes: 67 additions & 0 deletions src/authConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { LogLevel } from "@azure/msal-browser";

/**
* Configuration object to be passed to MSAL instance on creation.
* For a full list of MSAL.js configuration parameters, visit:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
*/

export const msalConfig = {
auth: {
clientId: "Enter_the_Application_Id_Here",
authority: "https://login.microsoftonline.com/Enter_the_Tenant_Info_Here",
redirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
return;
}
}
}
}
};

/**
* Scopes you add here will be prompted for user consent during sign-in.
* By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
* For more information about OIDC scopes, visit:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
export const loginRequest = {
scopes: ["user.read"]
};

/**
* Add here the scopes to request when obtaining an access token for MS Graph API. For more information, see:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
*/
export const graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
};
40 changes: 40 additions & 0 deletions src/components/PageLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import React from 'react';
import Navbar from 'react-bootstrap/Navbar';

import { useIsAuthenticated } from '@azure/msal-react';
import { SignInButton } from './SignInButton';
import { SignOutButton } from './SignOutButton';

/**
* Renders the navbar component with a sign-in or sign-out button depending on whether or not a user is authenticated
* @param props
*/
export const PageLayout = (props) => {
const isAuthenticated = useIsAuthenticated();

return (
<>
<Navbar bg="primary" variant="dark" className="navbarStyle">
<a className="navbar-brand" href="/">
Microsoft Identity Platform
</a>
<div className="collapse navbar-collapse justify-content-end">
{isAuthenticated ? <SignOutButton /> : <SignInButton />}
</div>
</Navbar>
<div className="title">
<h5>
Welcome to the Microsoft Authentication Library For Javascript - React SPA Tutorial
</h5>
</div>
<div className="profileContent">
{props.children}
</div>
</>
);
};
16 changes: 16 additions & 0 deletions src/components/ProfileData.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

/**
* Renders information about the user obtained from MS Graph
* @param props
*/
export const ProfileData = (props) => {
return (
<div id="profile-div">
<p><strong>First Name: </strong> {props.graphData.givenName}</p>
<p><strong>Last Name: </strong> {props.graphData.surname}</p>
<p><strong>Email: </strong> {props.graphData.userPrincipalName}</p>
<p><strong>Id: </strong> {props.graphData.id}</p>
</div>
);
};
30 changes: 30 additions & 0 deletions src/components/SignInButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../authConfig";
import DropdownButton from "react-bootstrap/DropdownButton";
import Dropdown from "react-bootstrap/Dropdown";

/**
* Renders a drop down button with child buttons for logging in with a popup or redirect
*/
export const SignInButton = () => {
const { instance } = useMsal();

const handleLogin = (loginType) => {
if (loginType === "popup") {
instance.loginPopup(loginRequest).catch(e => {
console.log(e);
});
} else if (loginType === "redirect") {
instance.loginRedirect(loginRequest).catch(e => {
console.log(e);
});
}
}
return (
<DropdownButton variant="secondary" className="ml-auto" drop="start" title="Sign In">
<Dropdown.Item as="button" onClick={() => handleLogin("popup")}>Sign in using Popup</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => handleLogin("redirect")}>Sign in using Redirect</Dropdown.Item>
</DropdownButton>
)
}
32 changes: 32 additions & 0 deletions src/components/SignOutButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
import { useMsal } from "@azure/msal-react";
import DropdownButton from "react-bootstrap/DropdownButton";
import Dropdown from "react-bootstrap/Dropdown";

/**
* Renders a sign-out button
*/
export const SignOutButton = () => {
const { instance } = useMsal();

const handleLogout = (logoutType) => {
if (logoutType === "popup") {
instance.logoutPopup({
postLogoutRedirectUri: "/",
mainWindowRedirectUri: "/"
});
} else if (logoutType === "redirect") {
instance.logoutRedirect({
postLogoutRedirectUri: "/",
});
}
}


return (
<DropdownButton variant="secondary" className="ml-auto" drop="start" title="Sign Out">
<Dropdown.Item as="button" onClick={() => handleLogout("popup")}>Sign out using Popup</Dropdown.Item>
<Dropdown.Item as="button" onClick={() => handleLogout("redirect")}>Sign out using Redirect</Dropdown.Item>
</DropdownButton>
)
}
21 changes: 21 additions & 0 deletions src/graph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { graphConfig } from "./authConfig";

/**
* Attaches a given access token to a MS Graph API call. Returns information about the user
* @param accessToken
*/
export async function callMsGraph(accessToken) {
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;

headers.append("Authorization", bearer);

const options = {
method: "GET",
headers: headers
};

return fetch(graphConfig.graphMeEndpoint, options)
.then(response => response.json())
.catch(error => console.log(error));
}
Loading