1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

node.jsでAzureADを認証基盤として利用する

Last updated at Posted at 2021-12-31

初めての投稿

初めて記事を投稿します
いつもお世話になっているので、これからは少しでもアウトプットしていきたいと思います

記事の中でやっていること

  • 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へのリンクです
TopPage

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 認証画面
ユーザ認証が正常に行われると、AzureADはクエリパラメータにトークンリクエストコードを含めてredirectUri: REDIRECT_URIにリダイレクトします

アカウント情報を取得し、クッキーに保存する

/redirectのクエリパラメータからリクエストコードcode: req.query.codeを取得します
それを使ってcca.acquireTokenByCodeを実行することで、アカウント情報とIDトークン、アクセストークンが responseとして得られます
今回はアカウント情報からaccount.usernameaccount.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}!`))

ソースコードまとめ

最後にソースコードをまとめて貼っておきます

index.js
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}!`))

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?