Googleの検索ランキングアルゴリズムのシグナルの一つやGoogle Chromeが常時SSL化をするようになったことで、HTTPSでの通信をおこなうことが多くなってきました。それに伴い普段の開発環境でも自己証明書等を導入して、HTTPSでの通信を行うための開発環境を構築して開発を行っていることもあるかと思います。今回は、そのような開発環境で発生したunable to verify the first certificateというエラーに対する対策について考えてみます。

エラー発生状況

PromiseをベースとしたHTTP Clientライブラリaxisoを使ってSSL化されたAPIサーバにアクセスした際にunable to verify the first certificateというエラーが発生しました。環境については、該当する箇所をまとめると下記のようになっていました。

  • mkcertで作成した自己証明書
  • 自己証明書を読み込んだAPIサーバ
  • HTTP Client(NuxtJSの画面)

発生していたエラー 

SPAで作成された画面とAPIサーバがあって、画面とAPIサーバのやりとりの間で発生していたエラーです。実際のエラーログは、下記のように表示されていました。

unable to verify the first certificate                                                                08:14:37

  at TLSSocket.onConnectSecure (_tls_wrap.js:1473:34)
  at TLSSocket.emit (events.js:311:20)
  at TLSSocket.EventEmitter.emit (domain.js:482:12)
  at TLSSocket._finishInit (_tls_wrap.js:916:8)
  at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:686:12)

このエラーが発生したときのソースコードは、下記のようなシンプルなものです。
サーバ側でオレオレ証明書を読み込んでいます。

try {
  const res = await axios.get('https://localhost:8080/hello')
  console.log(res)
} catch (error) {
  console.log(error)
}

サーバサイドのコード

import express from 'express'
import https from 'https'
import fs from 'fs'

const opsions = {
  key: fs.readFileSync('../conf/cert/localhost-key.pem'),
  cert: fs.readFileSync('../conf/cert/localhost.pem')
}

const app: express.Express = express()
const server = https.createServer(opsions, app)

const router: express.Router = express.Router()

router.get('/hello', async (req:express.Request, res:express.Response) => {
  res.status(200).json( {
    message: 'hello'
  })
  return
})

app.use('/api', router)

const port = 8000
server.listen(8000,()=>{
  console.log(`Listening on port ${port}...`)
})

先ほど紹介したエラーログは、axios.get('https://localhost:8080/hello')でリクエストした結果で発生していました。
サーバ側で使っている証明書のverifyができないとのことです。今回使っていた証明書は、いわゆるオレオレ証明書で手抜きでつくったものなので、証明書の検証に失敗しているようです。実際に開発環境ではなく、Cloud Functions等で同じロジックを動かして、そちらへ接続するとhttps Status 200がかえってくることが分かりました。

こちらのhttps://github.com/axios/axios/issues/535#issuecomment-260841069を参考にして、今回は、SSLのverifyをスキップする方法をしました。const httpsAgent = new https.Agent({ rejectUnauthorized: false })という形のhttpsAgentをadios.get呼び出しで指定して上げる形とし、無事解決しました。また、axios自体は、node標準のhttp,httpsライブラリのアダプターにもなっているので、http,httpsライブラリでも同じような対処が可能です。

まとめ

最近では、常時SSL化が当たり前となり開発時にも環境周りに引きづられることが多くなりました。環境周りについての深い理解を深めることと開発の効率化という難しい状況になりつつありますが、楽しく開発していきたいものです。