初めての投稿
初めて記事を投稿します
いつもお世話になっているので、これからは少しでもアウトプットしていきたいと思います
記事の中でやっていること
- AzureADを認証基盤として使う
- AzureADから取得したアカウント情報をcookieに保存する
- cookieに保存したアカウント情報を取得する
環境
- ubuntu 20.04
- node v16.13.1
AzureADへのアプリケーション登録
AzureADを認証基盤として利用したいアプリをAzurePortalに登録します(登録方法は本家(Microsoft)を参照)
今回はアプリ登録時に設定するリダイレクトURIにhttp://localhost:3000/redirect
を設定します
AzurePortalにアプリを登録することで、アプリのクライアントIDとクライアントシークレットを取得することができます
MSAL Node
承認コードフロー用のnpmパッケージをインストールします
npm install @azure/msal-node
その他のnpmパッケージ
ルーティング、画面表示、cookie取得などのためにインストールしたnpmパッケージは以下の通りです
npm install express express-ejs-layouts ejs cookie-parser
サンプルアプリ
msal の設定
後でmsalオブジェクトに渡すためにREDIRECT_URI
にAzurePortalで登録したhttp://localhost:3000/redirect
を設定します
system:
の部分はログを出力するための設定です(あまり詳しくないです...)
config
を使ってmsalオブジェクトcca
を作成します
const msal = require('@azure/msal-node')
const express = require('express'),
expressEjsLayouts = require('express-ejs-layouts'),
cookieParser = require('cookie-parser')
const SERVER_PORT = process.env.PORT || 3000
const REDIRECT_URI = 'http://localhost:3000/redirect'
const config = {
auth: {
clientId: '自分のアプリのクライアントID',
authority: 'https://login.microsoftonline.com/common',
clientSecret: '自分のアプリのクライアントシークレット'
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message)
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose
}
}
}
// Create msal application object
const cca = new msal.ConfidentialClientApplication(config)
expressとcookieの設定
expressでejs(テンプレートエンジン)とcookieを扱えるようにするための設定です
const app = express()
app.set('view engine', 'ejs')
app.use(cookieParser())
app.use(expressEjsLayouts)
app.use(express.static('public'))
const cookieSettings = {
cookie:{
maxAge: 3600 //秒
},
resave: false,
saveUninitialized: false
}
Top Page
特に理由はありませんがTop Pageを設定しました
app.get('/', (req, res) => {
res.render('index',{title: 'Top Page'})
console.log(req.headers)
})
スクリーンショットはこんな感じです
青いAzure AD認証
は/auth
へのリンクです
AzureADの認証画面をユーザに表示
認証に使用するスコープとリダイレクトURIを設定します
登録したアプリが利用できるスコープはAzurePortalの画面に表示されています
user.read
はアカウント情報の読み取りのみが可能です
ユーザが/auth
にアクセスするとcca.getAuthCodeUrl
が実行され、ユーザが認証を行うための画面へのリンクがresponse
として得られるので、それをリダイレクトします。
app.get('/auth', (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read'],
redirectUri: REDIRECT_URI
}
// get url to sign user in and consent to scopes needed for application
cca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
console.log(`\nResponse:\n${response}\n`)
res.redirect(response)
}).catch((error) => console.log(JSON.stringify(error)))
})
リダイレクト先としてユーザにAzureADの認証画面が表示されます
ただし、既にブラウザでAzureADにサインインしている場合は、認証画面は表示されず自動的に次のステップに進みます
ユーザ認証が正常に行われると、AzureADはクエリパラメータにトークンリクエストコードを含めてredirectUri: REDIRECT_URI
にリダイレクトします
アカウント情報を取得し、クッキーに保存する
/redirect
のクエリパラメータからリクエストコードcode: req.query.code
を取得します
それを使ってcca.acquireTokenByCode
を実行することで、アカウント情報とIDトークン、アクセストークンが response
として得られます
今回はアカウント情報からaccount.username
とaccount.name
を取得してres.cookie
でレスポンスにcookieを設定し、/user
へリダイレクトしています
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ['user.read'],
redirectUri: REDIRECT_URI,
}
console.log(`\nCode:\n${tokenRequest.code}\n`)
cca.acquireTokenByCode(tokenRequest).then((response) => {
console.log("\nResponse: \n:", response)
const userName = response.account.username
const name = response.account.name
res.cookie('username', userName, cookieSettings)
res.cookie('name', name, cookieSettings)
res.redirect('/user')
}).catch((error) => {
console.log(error)
res.status(500).send(error)
})
})
cookieからアカウント情報を取得
/user
へのリクエストには先ほど設定したcookieが含まれているため、そのcookieからアカウント情報を取り出し、画面に表示しています
今回はcookieにアカウント情報を平文で保存していますが、cookieはブラウザで簡単に編集ができるため実用には暗号化などの対策が必要だと思います
app.get('/user', (req, res) => {
const data = req.cookies
console.log(data)
res.render('user', {title: 'User Info', userName: data.username, name: data.name})
})
サーバの起動
サーバを起動します
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
ソースコードまとめ
最後にソースコードをまとめて貼っておきます
const msal = require('@azure/msal-node')
const express = require('express'),
expressEjsLayouts = require('express-ejs-layouts'),
cookieParser = require('cookie-parser')
const SERVER_PORT = process.env.PORT || 3000
const REDIRECT_URI = 'http://localhost:3000/redirect'
const config = {
auth: {
clientId: '自分のアプリのクライアントID',
authority: 'https://login.microsoftonline.com/common',
clientSecret: '自分のアプリのクライアントシークレット'
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message)
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose
}
}
}
// Create msal application object
const cca = new msal.ConfidentialClientApplication(config)
const app = express()
app.set('view engine', 'ejs')
app.use(cookieParser())
app.use(expressEjsLayouts)
app.use(express.static('public'))
const cookieSettings = {
cookie:{
maxAge: 3600 //秒
},
resave: false,
saveUninitialized: false
}
app.get('/', (req, res) => {
res.render('index',{title: 'Top Page'})
console.log(req.headers)
})
app.get('/auth', (req, res) => {
const authCodeUrlParameters = {
scopes: ['user.read'],
redirectUri: REDIRECT_URI
}
// get url to sign user in and consent to scopes needed for application
cca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
console.log(`\nResponse:\n${response}\n`)
res.redirect(response)
}).catch((error) => console.log(JSON.stringify(error)))
})
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ['user.read'],
redirectUri: REDIRECT_URI,
}
console.log(`\nCode:\n${tokenRequest.code}\n`)
cca.acquireTokenByCode(tokenRequest).then((response) => {
console.log("\nResponse: \n:", response)
const userName = response.account.username
const name = response.account.name
res.cookie('username', userName, cookieSettings)
res.cookie('name', name, cookieSettings)
res.redirect('/user')
}).catch((error) => {
console.log(error)
res.status(500).send(error)
})
})
app.get('/user', (req, res) => {
const data = req.cookies
console.log(data)
res.render('user', {title: 'User Info', userName: data.username, name: data.name})
})
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))