0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JWTを用いてAPI使用時にアクセストークンを発行し、セキュリティ強度を確保する方法

Last updated at Posted at 2024-12-06

個人情報を含むデータをAPIでやり取りする際に、
アクセストークンを発行し、認証する段階を踏むことでセキュリティ強度を確保したい。

※JWTを扱うために、System.IdentityModel.Tokens.Jwt というNuGetパッケージをインストールする

取得データのやり取りの前にアクセストークンを発行し、以降の通信時にはこのアクセストークンをBearer認証にしようする。

アクセストークン発行

リクエスト側
・メソッド:POST
・ボディ:JSON

{
    "PersonalID": "xxxxxxxxxxxxxxxxxxx"
}

レスポンス側
1.JWTのシークレットキーと署名クレデンシャルを定義する
2.クレーム(JWTのペイロードに含めるデータ)を作成する
3.トークンを発行する
4.生成したトークンをレスポンスする

※レイヤードアーキテクチャ(Controller,DataSet,Param,Resに分かれている)で実装

CreateTokenController.vb
Imports System.Web.Http
Imports System.Text.Json
Imports System.IdentityModel.Tokens.Jwt
Imports Microsoft.IdentityModel.Tokens
Imports System.Security.Claims
Imports System.Security.Cryptography
Imports System.Text

''' <summary>
''' アクセストークン取得API
''' </summary>
Public Class CreateTokenController
    Public Function PostValue(<FromBody()> param As CreateTokenControllerParam) As CreateTokenControllerRes

        Dim res As New CreateTokenControllerRes

        Try

            'パラメータチェック
            If CheckParam(param) = False Then
                res.Result = False
                res.ErrorCd = "E01"
                res.ErrorMessage = "パラメータが不正です。"
                Return res
            End If

            'トークンを生成
            Dim token As String = GenerateJwtToken(PersonalID)

            res.AccessToken = token
            res.Expiration = 3600
            res.TokenType = "Bearer"
            res.Result = True

        Catch ex As Exception
            res.Result = False
            res.ErrorCd = "E99"
            res.ErrorMessage = "予期せぬエラーが発生(" & ex.Message & ")"
        End Try
        Return res
    End Function


    ''' <summary>
    ''' パラメータチェック
    ''' </summary>
    ''' <param name="param"></param>
    ''' <returns></returns>
    Private Function CheckParam(ByVal param As SE02_04Param) As Boolean
        If param Is Nothing Then
            Return False
        End If

        If String.IsNullOrEmpty(param.PersonalID) Then
            Return False
        End If

        Return True
    End Function


    ''' <summary>
    ''' アクセストークン生成
    ''' </summary>
    ''' <param name="contClientCd"></param>
    ''' <param name="pbxCd"></param>
    ''' <returns></returns>
    Private Function GenerateJwtToken(PersonalID As String) As String
        ' クレームの作成
        Dim claims As New List(Of Claim) From {
                New Claim("PersonalID", personalID)
            }

        ' シークレットキー
        Dim key As New SymmetricSecurityKey(Encoding.UTF8.GetBytes(CommonConst.JWT_KEY))

        ' トークン署名用の資格情報
        Dim credentials As New SigningCredentials(key, SecurityAlgorithms.HmacSha256)

        ' トークンの生成
        Dim token As New JwtSecurityToken(
                issuer:="MyApp",                ' 発行者
                audience:="YourApp",              ' 受信者
                claims:=claims,                   ' クレームのリスト
                expires:=DateTime.Now.AddSeconds(3600), ' 有効期限(3600s)
                signingCredentials:=credentials   ' 署名情報
            )

        ' トークンを文字列として返す
        Return New JwtSecurityTokenHandler().WriteToken(token)
    End Function
End Class

CreateTokenParam.vb
Imports System.Runtime.Serialization
<DataContract([Namespace]:="")>
Public Class CreateTokenParam
    <DataMember()>
    Public Property PersonalID As String
End Class
CreateTokenRes.vb
Imports System.Runtime.Serialization

<DataContract([Namespace]:="")>
Public Class CreateTokenRes
    <DataMember()>
    Public Property Result As Boolean = True

    <DataMember()>
    Public Property ErrorCd As String

    <DataMember()>
    Public Property ErrorMessage As String

    <DataMember()>
    Public Property AccessToken As String

    <DataMember()>
    Public Property Expiration As Integer

    <DataMember()>
    Public Property TokenType As String


    Public Sub New()
        Result = False
        ErrorCd = String.Empty
        ErrorMessage = String.Empty
        AccessToken = String.Empty
        Expiration = 0
        TokenType = String.Empty
    End Sub
End Class

APIを使用してデータを取得

リクエスト側
・Bearer認証を用いる
・メソッド:GET or POST(例ではGETを使用)
・Authorizationヘッダーかカスタムヘッダーに「Beare アクセストークンの値」をセットする

レスポンス側

PersonalInfoAPIController.vb
Imports System.Text
Imports System.IdentityModel.Tokens.Jwt
Imports Microsoft.IdentityModel.Tokens
Imports System.Security.Claims
Imports System.Net.Http
Imports System.Web.Http

Public Class PersonalInfoAPIController

    Public Function GetValue() As PersonalInfoAPIRes

        Dim res As New PersonalInfoRes

        Try
            ' カスタムヘッダー名 My-Authorization からトークンを取得する
            Dim authHeader As IEnumerable(Of String)

            If Not Request.Headers.TryGetValues("My-Authorization", authHeader) Then
                res.Result = False
                res.ErrorCd = "E99"
                res.ErrorMessage = "ヘッダーを取得できませんでした。"
                Return res
            End If

            ' 最初の値を取得
            Dim token As String = authHeader.FirstOrDefault()

            ' Authorizationヘッダーが存在しない、または形式が正しくない場合は認証失敗
            If String.IsNullOrEmpty(token) OrElse Not token.StartsWith("Bearer ") Then
                res.Result = False
                res.ErrorCd = "E99"
                res.ErrorMessage = "Authorizationヘッダーが存在しない、または形式が正しくありません。"
                Return res
            End If

            ' Bearer トークン部分のみを抽出
            token = token.Substring("Bearer ".Length).Trim()

            ' トークンを検証する
            Dim validatedToken As SecurityToken
            Dim claimsPrincipal As ClaimsPrincipal = JwtTokenAuthUtil.ValidateJwtToken(token, validatedToken)

            ' トークンが無効であれば認証失敗
            If claimsPrincipal Is Nothing Then
                res.Result = False
                res.ErrorCd = "E03"
                res.ErrorMessage = "トークンの認証に失敗しました。"
                Return res
            End If

            ' クレームからPersonalIDを取得
            Dim claim As Claim = claimsPrincipal.FindFirst("PersonalID")
            Dim personalID As String = String.Empty
            If claim IsNot Nothing Then
                personalID = claim.Value
            End If

            '内線グループ取得
            Dim personalInfoList As List(Of PersonalInfoList) = GetPersonalInfo(dbCnStr, personalID)

            res.Result = True
            res.PersonalInfoList = personalInfoList

        Catch ex As Exception
            res.Result = False
            res.ErrorCd = "E99"
            res.ErrorMessage = "予期せぬエラーが発生しました。"
        End Try

        Return res

    End Function


    Private Function GetPersonalInfo(dbCnStr As String, personalID As String) As List(Of PersonalInfoList)
        '初期化
        Dim list As New List(Of PersonalInfoList)

        Using ta As New dsPersonalInfoTableAdapters.T_PersonalInfoTableAdapter(dbCnStr)
            Dim dt As New dsPersonalInfo.T_PersonalInfoDataTable
            ta.Fill(dt, personalID)
            For Each dr As dsPersonalInfo.T_PersonalInfoRow In dt

                Dim PersonalID = dr.PersonalID
                Dim Name = dr.Name
                Dim Age = dr.Age

                list.Add(New PersonalInfoList(PersonalID, Name, Age))
            Next
        End Using

        Return list
    End Function

End Class
PersonalInfoAPIRes.vb
Imports System.Runtime.Serialization

<DataContract([Namespace]:="")>
Public Class PersonalInfoAPIRes
	<DataMember()>
	Public Property Result As Boolean = True

	<DataMember()>
	Public Property ErrorCd As String

	<DataMember()>
	Public Property ErrorMessage As String

	<DataMember()>
	Public Property PersonalInfoList As List(Of PersonalInfoList)

	Public Sub New()
		Result = False
		ErrorCd = String.Empty
		ErrorMessage = String.Empty
	End Sub

End Class

Public Class PersonalInfoList
	Public Sub New(personalID As String,
				   name As String,
				   age As Integer)

		Me.PersonalID = personalID
		Me.Name = name
		Me.Age = age
	End Sub

	Public Property PersonalID As String

	Public Property Name As String

	Public Property Age As Integer

End Class
{
    "Result": "True",
    "ErrorCd": "",
    "ErrorMessage": "",
    "PersonalInfoList": [
        {
            "PersonalID": 1,
            "Name": "桃太郎",
            "Age": 1
        }
    ]
}
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?