Skip to content
This repository was archived by the owner on May 3, 2024. It is now read-only.

Commit 9a9ee1e

Browse files
committed
added loginPop with loginHint
1 parent 175e83b commit 9a9ee1e

File tree

6 files changed

+159
-76
lines changed

6 files changed

+159
-76
lines changed

6-AdvancedScenarios/4-hybrid-SPA/App/client/src/App.jsx

+22-15
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,40 @@ export const App = ({ instance }) => {
2929
const { inProgress } = useMsal();
3030
const [data, setdata] = useState(null);
3131

32+
/**
33+
* We render the SPA code that was acquired server-side, and provide it to the acquireTokenByCode API on the MSAL.js PublicClientApplication instance.
34+
* The application should also render any account hints, as they will be needed for any interactive requests to ensure the same user is used for both requests.
35+
* For more information about using loginHint and sid, visit:
36+
* https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-core#2-login-the-user
37+
*/
3238
useEffect(() => {
33-
34-
if(getCode){
39+
if(getCode && !data){
3540
callApiToGetSpaCode()
36-
.then((data) => {
41+
.then((response) => {
3742
if(inProgress === "none"){
38-
// SPA auth code
39-
let code = data.code;
40-
instance.acquireTokenByCode({code})
41-
.then((res) => {
43+
const { code, loginHint, sid, referredUsername } = response;
44+
instance.acquireTokenByCode({
45+
code
46+
}).then((res) => {
4247
setdata(res)
43-
}).catch((error) => {
48+
}).catch((error ) => {
4449
if(error instanceof InteractionRequiredAuthError){
4550
if (inProgress === "none") {
46-
instance.acquireTokenPopup({code})
47-
.then((res) => {
48-
setdata(res)
49-
}).catch((error) => {
50-
console.log(error)
51-
})
51+
//If loginHint claim is provided, dont use sid
52+
instance.loginPopup({
53+
loginHint //Prefer loginHint claim over referredUsername (email)
54+
}).then((res) => {
55+
setdata(res)
56+
}).catch((error) => {
57+
console.log(error)
58+
})
5259
}
5360
}
5461
})
5562
}
5663
})
5764
}
58-
});
65+
}, [instance, inProgress]);
5966

6067
/**
6168
* msal-react is built on the React context API and all parts of your app that require authentication must be

6-AdvancedScenarios/4-hybrid-SPA/App/controllers/authController.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,21 @@ exports.handleRedirectWithCode = (req, res) => {
3232

3333
msalInstance.acquireTokenByCode(tokenRequest)
3434
.then((response) => {
35-
const { code } = response;
35+
36+
const { code } = response; //SPA authorization code
37+
const {
38+
sid, // Session ID claim, used for non-hybrid
39+
login_hint: loginHint, // New login_hint claim (used instead of sid or email)
40+
preferred_username: preferredUsername // Email
41+
} = response.idTokenClaims;
42+
43+
3644
req.session.code = code;
45+
req.session.loginHint = loginHint;
46+
req.session.sid = sid;
47+
req.session.referredUsername = preferredUsername;
3748
req.session.authenticated = true;
49+
3850
const urlFrom = (urlObject) => String(Object.assign(new URL("http://localhost:5000"), urlObject))
3951
res.redirect(urlFrom({
4052
protocol: 'http',
@@ -55,7 +67,7 @@ exports.logoutUser = (req, res) => {
5567
exports.sendSPACode = (req, res) => {
5668

5769
if(req.session.authenticated) {
58-
res.status(200).json({code: req.session.code});
70+
res.status(200).json({ ...req.session});
5971
}else {
6072
res.status(401).json({ message: "user is not authenticated"})
6173
}

6-AdvancedScenarios/4-hybrid-SPA/AppCreationScripts/Configure.ps1

+2-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ Function ConfigureApplications
196196
} `
197197
-Spa `
198198
@{ `
199-
RedirectUris = "http://localhost:5000"
199+
RedirectUris = "http://localhost:5000";
200200
} `
201201
-SignInAudience AzureADMyOrg `
202202
#end of command
@@ -315,6 +315,7 @@ Function ConfigureApplications
315315
Write-Host "- For service"
316316
Write-Host " - Navigate to $servicePortalUrl"
317317
Write-Host " - Navigate to the Manifest page, find the property 'accessTokenAcceptedVersion' and set it to '2'" -ForegroundColor Red
318+
Write-Host " - Navigate to the Manifest page, find the 'optionalClaims' section and change its default value to request 'idToken' claims" -ForegroundColor Red
318319
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
319320
if($isOpenSSL -eq 'Y')
320321
{

6-AdvancedScenarios/4-hybrid-SPA/AppCreationScripts/sample.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@
5555
"ManualSteps": [
5656
{
5757
"Comment": "Navigate to the Manifest page, find the property 'accessTokenAcceptedVersion' and set it to '2'"
58+
},
59+
{
60+
"Comment": "Navigate to the Manifest page, find the 'optionalClaims' section and change its default value to request 'idToken' claims"
61+
5862
}
5963
],
60-
"RequiredResourcesAccess": [
64+
"RequiredResourcesAccess": [
6165
{
6266
"Resource": "Microsoft Graph",
6367
"DelegatedPermissions": [

6-AdvancedScenarios/4-hybrid-SPA/README.md

+77-37
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,28 @@ The first thing that we need to do is to declare the unique [resource](https://d
171171
* Select the **Add scope** button on the bottom to save this scope.
172172
1. Select the `Manifest` blade on the left.
173173
* Set `accessTokenAcceptedVersion` property to **2**.
174+
* Set the `optionalClaims` property as shown below to request client capabilities claim `idToken`:
175+
```json
176+
"optionalClaims": {
177+
"idToken": [
178+
{
179+
"name": "sid",
180+
"source": null,
181+
"essential": false,
182+
"additionalProperties": []
183+
},
184+
{
185+
"name": "login_hint",
186+
"source": null,
187+
"essential": false,
188+
"additionalProperties": []
189+
}
190+
],
191+
"accessToken": [],
192+
"saml2Token": []
193+
}
194+
```
195+
174196
* Click on **Save**.
175197

176198
#### Configure the service app (msal-hybrid-spa) to use your app registration
@@ -252,28 +274,43 @@ Next, parse the authorization code, and invoke the acquireTokenByCode API on the
252274

253275
When invoking this API, set enableSpaAuthorizationCode to true, which will enable MSAL to acquire a second authorization code to be redeemed by your single-page application.
254276

277+
Your application should parse this second authorization code, as well as any account hints (e.g. sid, login_hint, preferred_username) and return them such that they can be rendered client-side:
278+
255279
```javascript
256-
const tokenRequest = {
257-
code: req.body.code,
258-
redirectUri: process.env.REDIRECT_URI,
259-
enableSpaAuthorizationCode: true
260-
};
261-
262-
263-
msalInstance.acquireTokenByCode(tokenRequest)
264-
.then((response) => {
265-
const { code } = response;
266-
req.session.code = code;
267-
req.session.authenticated = true;
268-
const urlFrom = (urlObject) => String(Object.assign(new URL("http://localhost:5000"), urlObject))
269-
res.redirect(urlFrom({
270-
protocol: 'http',
271-
pathname: '/',
272-
search: 'getCode=true'
273-
}))
274-
}).catch((err) => {
275-
console.log(err)
276-
})
280+
const tokenRequest = {
281+
code: req.body.code,
282+
redirectUri: process.env.REDIRECT_URI,
283+
enableSpaAuthorizationCode: true
284+
};
285+
286+
287+
288+
msalInstance.acquireTokenByCode(tokenRequest)
289+
.then((response) => {
290+
291+
const { code } = response; //SPA authorization code
292+
const {
293+
sid, // Session ID claim, used for non-hybrid
294+
login_hint: loginHint, // New login_hint claim (used instead of sid or email)
295+
preferred_username: preferredUsername // Email
296+
} = response.idTokenClaims;
297+
298+
299+
req.session.code = code;
300+
req.session.loginHint = loginHint;
301+
req.session.sid = sid;
302+
req.session.referredUsername = preferredUsername;
303+
req.session.authenticated = true;
304+
305+
const urlFrom = (urlObject) => String(Object.assign(new URL("http://localhost:5000"), urlObject))
306+
res.redirect(urlFrom({
307+
protocol: 'http',
308+
pathname: '/',
309+
search: 'getCode=true'
310+
}))
311+
}).catch((err) => {
312+
console.log(err)
313+
})
277314
```
278315

279316
### Public client
@@ -293,33 +330,36 @@ ReactDOM.render(
293330

294331
Next, render the code that was acquired server-side, and provide it to the acquireTokenByCode API on the MSAL.js PublicClientApplication instance.
295332

333+
The application should also render any account hints, as they will be needed for any interactive requests to ensure the same user is used for both requests.
334+
296335
```javascript
297-
seEffect(() => {
298-
if(getCode){
299-
callApiToGetSpaCode()
300-
.then((data) => {
336+
if(getCode && !data){
337+
callApiToGetSpaCode()
338+
.then((response) => {
301339
if(inProgress === "none"){
302-
// SPA auth code
303-
let code = data.code;
304-
instance.acquireTokenByCode({code})
305-
.then((res) => {
340+
const { code, loginHint, sid, referredUsername } = response;
341+
instance.acquireTokenByCode({
342+
code
343+
}).then((res) => {
306344
setdata(res)
307-
}).catch((error) => {
345+
}).catch((error ) => {
308346
if(error instanceof InteractionRequiredAuthError){
309347
if (inProgress === "none") {
310-
instance.acquireTokenPopup({code})
311-
.then((res) => {
312-
setdata(res)
313-
}).catch((error) => {
314-
console.log(error)
315-
})
348+
//If loginHint claim is provided, dont use sid
349+
instance.loginPopup({
350+
loginHint //Prefer loginHint claim over referredUsername (email)
351+
}).then((res) => {
352+
setdata(res)
353+
}).catch((error) => {
354+
console.log(error)
355+
})
316356
}
317357
}
318358
})
319359
}
320360
})
321361
}
322-
});
362+
}, [instance, inProgress]);
323363
```
324364

325365
## Troubleshooting

6-AdvancedScenarios/4-hybrid-SPA/ReadmeFiles/ReadmeAboutTheCode.md

+39-20
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,35 @@ Next, parse the authorization code, and invoke the acquireTokenByCode API on the
4444

4545
When invoking this API, set enableSpaAuthorizationCode to true, which will enable MSAL to acquire a second authorization code to be redeemed by your single-page application.
4646

47+
Your application should parse this second authorization code, as well as any account hints (e.g. sid, login_hint, preferred_username) and return them such that they can be rendered client-side:
48+
4749
```javascript
4850

49-
const tokenRequest = {
51+
const tokenRequest = {
5052
code: req.body.code,
51-
redirectUri: appSettings.appCredentials.redirectUri,
52-
enableSpaAuthorizationCode: appSettings.appCredentials.enableSpaAuthorizationCode
53+
redirectUri: process.env.REDIRECT_URI,
54+
enableSpaAuthorizationCode: true
5355
};
5456

57+
58+
5559
msalInstance.acquireTokenByCode(tokenRequest)
5660
.then((response) => {
5761

58-
const { code } = response;
62+
const { code } = response; //SPA authorization code
63+
const {
64+
sid, // Session ID claim, used for non-hybrid
65+
login_hint: loginHint, // New login_hint claim (used instead of sid or email)
66+
preferred_username: preferredUsername // Email
67+
} = response.idTokenClaims;
68+
69+
5970
req.session.code = code;
71+
req.session.loginHint = loginHint;
72+
req.session.sid = sid;
73+
req.session.referredUsername = preferredUsername;
74+
req.session.authenticated = true;
75+
6076
const urlFrom = (urlObject) => String(Object.assign(new URL("http://localhost:5000"), urlObject))
6177
res.redirect(urlFrom({
6278
protocol: 'http',
@@ -86,31 +102,34 @@ ReactDOM.render(
86102

87103
Next, render the code that was acquired server-side, and provide it to the acquireTokenByCode API on the MSAL.js PublicClientApplication instance.
88104

105+
The application should also render any account hints, as they will be needed for any interactive requests to ensure the same user is used for both requests.
106+
89107
```javascript
90-
seEffect(() => {
91-
if(getCode){
92-
callApiToGetSpaCode()
93-
.then((data) => {
108+
if(getCode && !data){
109+
callApiToGetSpaCode()
110+
.then((response) => {
94111
if(inProgress === "none"){
95-
// SPA auth code
96-
let code = data.code;
97-
instance.acquireTokenByCode({code})
98-
.then((res) => {
112+
const { code, loginHint, sid, referredUsername } = response;
113+
instance.acquireTokenByCode({
114+
code
115+
}).then((res) => {
99116
setdata(res)
100-
}).catch((error) => {
117+
}).catch((error ) => {
101118
if(error instanceof InteractionRequiredAuthError){
102119
if (inProgress === "none") {
103-
instance.acquireTokenPopup({code})
104-
.then((res) => {
105-
setdata(res)
106-
}).catch((error) => {
107-
console.log(error)
108-
})
120+
//If loginHint claim is provided, dont use sid
121+
instance.loginPopup({
122+
loginHint //Prefer loginHint claim over referredUsername (email)
123+
}).then((res) => {
124+
setdata(res)
125+
}).catch((error) => {
126+
console.log(error)
127+
})
109128
}
110129
}
111130
})
112131
}
113132
})
114133
}
115-
});
134+
}, [instance, inProgress]);
116135
```

0 commit comments

Comments
 (0)