Hitelesítés engedélyezése a saját Node.js webes API-ban az Azure Active Directory B2C használatával
Ebből a cikkből megtudhatja, hogyan hozhatja létre a webalkalmazást, amely meghívja a webes API-t. A webes API-t az Azure Active Directory B2C -nek (Azure AD B2C-nek) kell védenie. A webes API-hoz való hozzáférés engedélyezéséhez olyan kéréseket szolgál ki, amelyek tartalmazzák a Azure AD B2C által kibocsátott érvényes hozzáférési jogkivonatot.
Előfeltételek
Mielőtt elkezdené elolvasni és elvégezni a hitelesítés konfigurálása egy minta Node.js webes API-ban a Azure AD B2C használatával című cikk lépéseit. Ezután kövesse az ebben a cikkben ismertetett lépéseket a minta webalkalmazás és a webes API saját webes API-ra való lecseréléséhez.
Visual Studio Code vagy más kódszerkesztő
1. lépés: Védett webes API létrehozása
Kövesse ezeket a lépéseket a Node.js webes API létrehozásához.
1.1. lépés: A projekt létrehozása
Webes API-k létrehozásához használja az ExpresstNode.js . Webes API létrehozásához tegye a következőket:
- Hozzon létre egy
TodoList
nevű mappát. TodoList
A mappa alatt hozzon létre egy nevű fájltindex.js
.- Egy parancshéjban futtassa a következőt
npm init -y
: . Ez a parancs létrehoz egy alapértelmezettpackage.json
fájlt a Node.js projekthez. - A parancshéjban futtassa a
npm install express
parancsot. Ez a parancs az Express keretrendszert telepíti.
1.2. lépés: Függőségek telepítése
Adja hozzá a hitelesítési kódtárat a webes API-projekthez. A hitelesítési kódtár elemzi a HTTP-hitelesítés fejlécét, ellenőrzi a jogkivonatot, és kinyeri a jogcímeket. További információért tekintse át a könyvtár dokumentációját.
A hitelesítési kódtár hozzáadásához telepítse a csomagokat a következő parancs futtatásával:
npm install passport
npm install passport-azure-ad
npm install morgan
A morgan csomag egy HTTP-kérésnaplózó köztes szoftver Node.js.
1.3. lépés: A webes API-kiszolgáló kódjának írása
A fájlban index.js
adja hozzá a következő kódot:
const express = require('express');
const morgan = require('morgan');
const passport = require('passport');
const config = require('./config.json');
const todolist = require('./todolist');
const cors = require('cors');
//<ms_docref_import_azuread_lib>
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
//</ms_docref_import_azuread_lib>
global.global_todos = [];
//<ms_docref_azureadb2c_options>
const options = {
identityMetadata: `https://${config.credentials.tenantName}.b2clogin.com/${config.credentials.tenantName}.onmicrosoft.com/${config.policies.policyName}/${config.metadata.version}/${config.metadata.discovery}`,
clientID: config.credentials.clientID,
audience: config.credentials.clientID,
policyName: config.policies.policyName,
isB2C: config.settings.isB2C,
validateIssuer: config.settings.validateIssuer,
loggingLevel: config.settings.loggingLevel,
passReqToCallback: config.settings.passReqToCallback
}
//</ms_docref_azureadb2c_options>
//<ms_docref_init_azuread_lib>
const bearerStrategy = new BearerStrategy(options, (token, done) => {
// Send user info using the second argument
done(null, { }, token);
}
);
//</ms_docref_init_azuread_lib>
const app = express();
app.use(express.json());
//enable CORS (for testing only -remove in production/deployment)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Authorization, Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use(morgan('dev'));
app.use(passport.initialize());
passport.use(bearerStrategy);
// To do list endpoints
app.use('/api/todolist', todolist);
//<ms_docref_protected_api_endpoint>
// API endpoint, one must present a bearer accessToken to access this endpoint
app.get('/hello',
passport.authenticate('oauth-bearer', {session: false}),
(req, res) => {
console.log('Validated claims: ', req.authInfo);
// Service relies on the name claim.
res.status(200).json({'name': req.authInfo['name']});
}
);
//</ms_docref_protected_api_endpoint>
//<ms_docref_anonymous_api_endpoint>
// API anonymous endpoint, returns a date to the caller.
app.get('/public', (req, res) => res.send( {'date': new Date() } ));
//</ms_docref_anonymous_api_endpoint>
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log('Listening on port ' + port);
});
Jegyezze fel a következő kódrészleteket a index.js
fájlban:
A passport Microsoft Entra könyvtár importálása
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
A Azure AD B2C beállításainak beállítása
const options = { identityMetadata: `https://${config.credentials.tenantName}.b2clogin.com/${config.credentials.tenantName}.onmicrosoft.com/${config.policies.policyName}/${config.metadata.version}/${config.metadata.discovery}`, clientID: config.credentials.clientID, audience: config.credentials.clientID, policyName: config.policies.policyName, isB2C: config.settings.isB2C, validateIssuer: config.settings.validateIssuer, loggingLevel: config.settings.loggingLevel, passReqToCallback: config.settings.passReqToCallback }
A passport Microsoft Entra könyvtár példányosítása a Azure AD B2C-beállításokkal
const bearerStrategy = new BearerStrategy(options, (token, done) => { // Send user info using the second argument done(null, { }, token); } );
A védett API-végpont. Olyan kéréseket szolgál ki, amelyek érvényes Azure AD B2C által kibocsátott hozzáférési jogkivonatot tartalmaznak. Ez a végpont a jogcím értékét
name
adja vissza a hozzáférési jogkivonaton belül.// API endpoint, one must present a bearer accessToken to access this endpoint app.get('/hello', passport.authenticate('oauth-bearer', {session: false}), (req, res) => { console.log('Validated claims: ', req.authInfo); // Service relies on the name claim. res.status(200).json({'name': req.authInfo['name']}); } );
A névtelen API-végpont. A webalkalmazás hozzáférési jogkivonat bemutatása nélkül hívhatja meg. A használatával névtelen hívásokkal hibakeresést végezhet a webes API-val.
// API anonymous endpoint, returns a date to the caller. app.get('/public', (req, res) => res.send( {'date': new Date() } ));
1.4. lépés: A webes API konfigurálása
Konfigurációk hozzáadása konfigurációs fájlhoz. A fájl információkat tartalmaz a Azure AD B2C-identitásszolgáltatóról. A web API-alkalmazás ezen információk alapján ellenőrzi a hozzáférési jogkivonatot, amelyet a webalkalmazás tulajdonosi jogkivonatként ad át.
A projekt gyökérmappájában hozzon létre egy
config.json
fájlt, majd adja hozzá a következő JSON-objektumot:{ "credentials": { "tenantName": "fabrikamb2c", "clientID": "93733604-cc77-4a3c-a604-87084dd55348" }, "policies": { "policyName": "B2C_1_susi" }, "resource": { "scope": ["tasks.read"] }, "metadata": { "authority": "login.microsoftonline.com", "discovery": ".well-known/openid-configuration", "version": "v2.0" }, "settings": { "isB2C": true, "validateIssuer": true, "passReqToCallback": false, "loggingLevel": "info" } }
A fájlban
config.json
frissítse a következő tulajdonságokat:
Section | Kulcs | Érték |
---|---|---|
hitelesítő adatok | tenantName | A Azure AD B2C-bérlő nevének első része (például fabrikamb2c ). |
hitelesítő adatok | clientID | A webes API-alkalmazás azonosítója. A webes API-alkalmazásregisztrációs azonosító beszerzéséről az Előfeltételek című témakörben olvashat. |
policies | policyName | A felhasználói folyamatok vagy egyéni szabályzatok. A felhasználói folyamat vagy szabályzat beszerzéséről az Előfeltételek című témakörben olvashat. |
erőforrás | scope | A webes API-alkalmazásregisztráció hatókörei, például [tasks.read] : . A webes API-hatókör beszerzéséről az Előfeltételek című témakörben olvashat. |
2. lépés: A webes Node-webalkalmazás létrehozása
Kövesse ezeket a lépéseket a Node-webalkalmazás létrehozásához. Ez a webalkalmazás hitelesíti a felhasználót egy hozzáférési jogkivonat beszerzéséhez, amely az 1. lépésben létrehozott Node webes API meghívására szolgál:
2.1. lépés: A csomópontprojekt létrehozása
Hozzon létre egy mappát a csomópontalkalmazás tárolásához, például call-protected-api
: .
A terminálban módosítsa a könyvtárat a csomópontalkalmazás mappájába( például
cd call-protected-api
), és futtassa a parancsotnpm init -y
. Ez a parancs egy alapértelmezett package.json fájlt hoz létre a Node.js-projekthez.A terminálban futtassa a
npm install express
parancsot. Ez a parancs az Express keretrendszert telepíti.Hozzon létre további mappákat és fájlokat a következő projektstruktúra eléréséhez:
call-protected-api/ ├── index.js └── package.json └── .env └── views/ └── layouts/ └── main.hbs └── signin.hbs └── api.hbs
A
views
mappa a webalkalmazás felhasználói felületéhez tartozó kezelősávfájlokat tartalmazza.
2.2. lépés: A függőségek telepítése
A terminálban telepítse a dotenv
, express-handlebars
, express-session
és @azure/msal-node
csomagokat az alábbi parancsok futtatásával:
npm install dotenv
npm install express-handlebars
npm install express
npm install axios
npm install express-session
npm install @azure/msal-node
2.3. lépés: Webalkalmazás felhasználói felületének összetevőinek létrehozása
A fájlban
main.hbs
adja hozzá a következő kódot:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <title>Azure AD B2C | Enable authenticate on web API using MSAL for B2C</title> <!-- adding Bootstrap 4 for UI components --> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link rel="SHORTCUT ICON" href="https://c.s-microsoft.com/favicon.ico?v2" type="image/x-icon"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <a class="navbar-brand" href="/">Microsoft Identity Platform</a> {{#if showSignInButton}} <div class="ml-auto"> <a type="button" id="SignIn" class="btn btn-success" href="/signin" aria-haspopup="true" aria-expanded="false"> Sign in to call PROTECTED API </a> <a type="button" id="SignIn" class="btn btn-warning" href="/api" aria-haspopup="true" aria-expanded="false"> Or call the ANONYMOUS API </a> </div> {{else}} <p class="navbar-brand d-flex ms-auto">Hi {{givenName}}</p> <a class="navbar-brand d-flex ms-auto" href="/signout">Sign out</a> {{/if}} </nav> <br> <h5 class="card-header text-center">MSAL Node Confidential Client application with Auth Code Flow</h5> <br> <div class="row" style="margin:auto" > {{{body}}} </div> <br> <br> </body> </html>
A
main.hbs
fájl alayout
mappában található, és minden olyan HTML-kódot tartalmaznia kell, amely az alkalmazás egészében szükséges. Implementálja a Bootstrap 5 CSS-keretrendszerrel létrehozott felhasználói felületet. Minden olyan felhasználói felület, amely lapról lapra változik( példáulsignin.hbs
), a helyőrzőben a következőként{{{body}}}
jelenik meg: .A fájlban
signin.hbs
adja hozzá a következő kódot:<div class="col-md-3" style="margin:auto"> <div class="card text-center"> <div class="card-body"> {{#if showSignInButton}} {{else}} <h5 class="card-title">You have signed in</h5> <a type="button" id="Call-api" class="btn btn-success" href="/api" aria-haspopup="true" aria-expanded="false"> Call the PROTECTED API </a> {{/if}} </div> </div> </div> </div>
A fájlban
api.hbs
adja hozzá a következő kódot:<div class="col-md-3" style="margin:auto"> <div class="card text-center bg-{{bg_color}}"> <div class="card-body"> <h5 class="card-title">{{data}}</h5> </div> </div> </div>
Ezen a lapon megjelenik az API válasza. A
bg-{{bg_color}}
Bootstrap kártyáján található osztályattribútum lehetővé teszi, hogy a felhasználói felület eltérő háttérszínt jelenítsen meg a különböző API-végpontok esetében.
2.4. lépés: Webalkalmazás-kiszolgáló kódának befejezése
A fájlban adja hozzá a
.env
következő kódot, amely tartalmazza a kiszolgáló http-portját, az alkalmazásregisztrációs adatokat, valamint a bejelentkezést és a felhasználói folyamat/szabályzat részleteit:SERVER_PORT=3000 #web apps client ID APP_CLIENT_ID=<You app client ID here> #session secret SESSION_SECRET=sessionSecretHere #web app client secret APP_CLIENT_SECRET=<Your app client secret here> #tenant name TENANT_NAME=<your-tenant-name> #B2C sign up and sign in user flow/policy name and authority SIGN_UP_SIGN_IN_POLICY_AUTHORITY=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name> AUTHORITY_DOMAIN=https://<your-tenant-name>.b2clogin.com #client redorect url APP_REDIRECT_URI=http://localhost:3000/redirect LOGOUT_ENDPOINT=https://<your-tenant-name>.b2clogin.com/<your-tenant-name>.onmicrosoft.com/<sign-in-sign-up-user-flow-name>/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000
A fájlok értékeinek módosítása a
.env
minta webalkalmazás konfigurálása című szakasz szerintindex.js
A fájlban adja hozzá a következő kódot:/* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ require('dotenv').config(); const express = require('express'); const session = require('express-session'); const {engine} = require('express-handlebars'); const msal = require('@azure/msal-node'); //Use axios to make http calls const axios = require('axios'); //<ms_docref_configure_msal> /** * Confidential Client Application Configuration */ const confidentialClientConfig = { auth: { clientId: process.env.APP_CLIENT_ID, authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, clientSecret: process.env.APP_CLIENT_SECRET, knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array redirectUri: process.env.APP_REDIRECT_URI, validateAuthority: false }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { console.log(message); }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Verbose, } } }; // Initialize MSAL Node const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig); //</ms_docref_configure_msal> // Current web API coordinates were pre-registered in a B2C tenant. //<ms_docref_api_config> const apiConfig = { webApiScopes: [`https://${process.env.TENANT_NAME}.onmicrosoft.com/tasks-api/tasks.read`], anonymousUri: 'http://localhost:5000/public', protectedUri: 'http://localhost:5000/hello' }; //</ms_docref_api_config> /** * The MSAL.js library allows you to pass your custom state as state parameter in the Request object * By default, MSAL.js passes a randomly generated unique state parameter value in the authentication requests. * The state parameter can also be used to encode information of the app's state before redirect. * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter. * For more information, visit: https://docs.microsoft.com/azure/active-directory/develop/msal-js-pass-custom-state-authentication-request */ const APP_STATES = { LOGIN: 'login', CALL_API:'call_api' } /** * Request Configuration * We manipulate these two request objects below * to acquire a token with the appropriate claims. */ const authCodeRequest = { redirectUri: confidentialClientConfig.auth.redirectUri, }; const tokenRequest = { redirectUri: confidentialClientConfig.auth.redirectUri, }; /** * Using express-session middleware. Be sure to familiarize yourself with available options * and set them as desired. Visit: https://www.npmjs.com/package/express-session */ const sessionConfig = { secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: false, // set this to true on production } } //Create an express instance const app = express(); //Set handlebars as your view engine app.engine('.hbs', engine({extname: '.hbs'})); app.set('view engine', '.hbs'); app.set("views", "./views"); app.use(session(sessionConfig)); /** * This method is used to generate an auth code request * @param {string} authority: the authority to request the auth code from * @param {array} scopes: scopes to request the auth code for * @param {string} state: state of the application, tag a request * @param {Object} res: express middleware response object */ const getAuthCode = (authority, scopes, state, res) => { // prepare the request console.log("Fetching Authorization code") authCodeRequest.authority = authority; authCodeRequest.scopes = scopes; authCodeRequest.state = state; //Each time you fetch Authorization code, update the authority in the tokenRequest configuration tokenRequest.authority = authority; // request an authorization code to exchange for a token return confidentialClientApplication.getAuthCodeUrl(authCodeRequest) .then((response) => { console.log("\nAuthCodeURL: \n" + response); //redirect to the auth code URL/send code to res.redirect(response); }) .catch((error) => { res.status(500).send(error); }); } app.get('/', (req, res) => { res.render('signin', { showSignInButton: true }); }); app.get('/signin',(req, res)=>{ //Initiate a Auth Code Flow >> for sign in //Pass the api scopes as well so that you received both the IdToken and accessToken getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,apiConfig.webApiScopes, APP_STATES.LOGIN, res); }); app.get('/redirect',(req, res)=>{ if (req.query.state === APP_STATES.LOGIN) { // prepare the request for calling the web API tokenRequest.authority = process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY; tokenRequest.scopes = apiConfig.webApiScopes; tokenRequest.code = req.query.code; confidentialClientApplication.acquireTokenByCode(tokenRequest) .then((response) => { req.session.accessToken = response.accessToken; req.session.givenName = response.idTokenClaims.given_name; console.log('\nAccessToken:' + req.session.accessToken); res.render('signin', {showSignInButton: false, givenName: response.idTokenClaims.given_name}); }).catch((error) => { console.log(error); res.status(500).send(error); }); }else{ res.status(500).send('We do not recognize this response!'); } }); //<ms_docref_api_express_route> app.get('/api', async (req, res) => { if(!req.session.accessToken){ //User is not logged in and so they can only call the anonymous API try { const response = await axios.get(apiConfig.anonymousUri); console.log('API response' + response.data); res.render('api',{data: JSON.stringify(response.data), showSignInButton: true, bg_color:'warning'}); } catch (error) { console.error(error); res.status(500).send(error); } }else{ //Users have the accessToken because they signed in and the accessToken is still in the session console.log('\nAccessToken:' + req.session.accessToken); let accessToken = req.session.accessToken; const options = { headers: { //accessToken used as bearer token to call a protected API Authorization: `Bearer ${accessToken}` } }; try { const response = await axios.get(apiConfig.protectedUri, options); console.log('API response' + response.data); res.render('api',{data: JSON.stringify(response.data), showSignInButton: false, bg_color:'success', givenName: req.session.givenName}); } catch (error) { console.error(error); res.status(500).send(error); } } }); //</ms_docref_api_express_route> /** * Sign out end point */ app.get('/signout',async (req, res)=>{ logoutUri = process.env.LOGOUT_ENDPOINT; req.session.destroy(() => { res.redirect(logoutUri); }); }); app.listen(process.env.SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port !` + process.env.SERVER_PORT));
A fájlban lévő
index.js
kód globális változókból és expressz útvonalakból áll.Globális változók:
confidentialClientConfig
: A bizalmas ügyfélalkalmazás-objektum létrehozásához használt MSAL konfigurációs objektum./** * Confidential Client Application Configuration */ const confidentialClientConfig = { auth: { clientId: process.env.APP_CLIENT_ID, authority: process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY, clientSecret: process.env.APP_CLIENT_SECRET, knownAuthorities: [process.env.AUTHORITY_DOMAIN], //This must be an array redirectUri: process.env.APP_REDIRECT_URI, validateAuthority: false }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { console.log(message); }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Verbose, } } }; // Initialize MSAL Node const confidentialClientApplication = new msal.ConfidentialClientApplication(confidentialClientConfig);
apiConfig
: Tulajdonságot tartalmazwebApiScopes
(az értéknek tömbnek kell lennie), amely a webes API-ban konfigurált és a webalkalmazásnak megadott hatókörök. Emellett URI-kkal is rendelkezik a meghívandó webes API-hoz, azaz ésanonymousUri
protectedUri
.const apiConfig = { webApiScopes: [`https://${process.env.TENANT_NAME}.onmicrosoft.com/tasks-api/tasks.read`], anonymousUri: 'http://localhost:5000/public', protectedUri: 'http://localhost:5000/hello' };
APP_STATES
: A kérésben szereplő érték, amelyet a jogkivonat válasza is visszaad. A Azure AD B2C-től kapott válaszok megkülönböztetésére szolgál.authCodeRequest
: Az engedélyezési kód lekéréséhez használt konfigurációs objektum.tokenRequest
: A jogkivonat engedélyezési kóddal történő beszerzéséhez használt konfigurációs objektum.sessionConfig
: Az expressz munkamenet konfigurációs objektuma.getAuthCode
: Az engedélyezési kérelem URL-címét létrehozó metódus, amely lehetővé teszi a felhasználó számára a hitelesítő adatok megadását és az alkalmazáshoz való hozzájárulást. A ConfidentialClientApplication osztályban definiált metódust használjagetAuthCodeUrl
.
Expressz útvonalak:
/
:- Ez a webalkalmazás bejegyzése, és megjeleníti a
signin
lapot.
- Ez a webalkalmazás bejegyzése, és megjeleníti a
/signin
:- Jelentkezzen be a felhasználóba.
- Meghívja
getAuthCode()
a metódust, és átadja aauthority
Bejelentkezési és regisztrációs felhasználói folyamatot/szabályzatot,APP_STATES.LOGIN
ésapiConfig.webApiScopes
azt. - Emiatt a végfelhasználónak meg kell adnia a bejelentkezési adatait, vagy ha a felhasználónak nincs fiókja, regisztrálhat.
- Az ebből a végpontból kapott végső válasz tartalmaz egy B2C-ből származó engedélyezési kódot, amelyet visszaküldött a
/redirect
végpontra.
/redirect
:- A végpont átirányítási URI-ként van beállítva a webalkalmazáshoz a Azure Portal.
- A lekérdezési paramétert Azure AD
state
B2C válaszában használja, hogy különbséget tegyen a webalkalmazásból érkező kérések között. - Ha az alkalmazás állapota ,
APP_STATES.LOGIN
a rendszer a beszerzett engedélyezési kódot használja egy jogkivonat lekéréséhez aacquireTokenByCode()
metódus használatával. Amikor tokent kér egy metódussalacquireTokenByCode
, ugyanazokat a hatóköröket használja, amelyeket az engedélyezési kód beszerzésekor használ. A beszerzett jogkivonat tartalmaz egyaccessToken
,idToken
ésidTokenClaims
. A beszerzéseaccessToken
után egy munkamenetbe helyezi a webes API meghívásához.
/api
:- Meghívja a webes API-t.
- Ha a
accessToken
nem a munkamenetben van, hívja meg a névtelen API-végpontot (http://localhost:5000/public
), ellenkező esetben hívja meg a védett API-végpontot (http://localhost:5000/hello
).
/signout
:- Kijelentkezteti a felhasználót.
- törli a webalkalmazás munkamenetét, és http-hívást kezdeményez a Azure AD B2C-kijelentkezési végpontra.
3. lépés: A webalkalmazás és az API futtatása
A webalkalmazás és a webes API teszteléséhez kövesse a Webalkalmazás és API futtatása című témakör lépéseit.