0
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?

LINE WORKS で OpenID Connect / Implicit Flow を試す

Posted at

5月に行われた LINE WORKS Developers のアップデートで、新たに OpenID Connect (OIDC) に対応しました。
OIDC では、ID 情報を含む ID Token を取得することが出来ます。
さらに、OIDC 利用時には、これまで OAuth で利用できた Authorzation Code Flow に加えて、Implicit Flow も利用できるようになりました。

今回は、Implicit Flow を使った OpenID Connect で、ID Token を取得してみます。

準備.1 : Scope の設定

OIDC を使用するには、アプリの OAuth Scopes で、openid scope を選択しておく必要があります。ID Token にメール情報も含めたい場合には email scope を、プロフィール情報も含めたい場合には profile scope を、追加で選択しておきます。

スクリーンショット 2024-05-31 201316.png

そういえば、今回のリリースで、Developer Console の画面もちょっとだけ変わりました。

準備.2 : WebDriver 入手

今回も、WebDriver を使っていきます。
こちら を参考にしてください。

1. WebDriver 起動

まずは WebDriver を起動します。

Starting Microsoft Edge WebDriver 127.0.2599.0 (c36d7542654f6489f48a0bed1a58b3f2d2f89c14) on port 9515
To submit feedback, report a bug, or suggest new features, please visit https://github.com/MicrosoftEdge/EdgeWebDriver

Only local connections are allowed.
Please see https://aka.ms/WebDriverSecurity for suggestions on keeping Microsoft Edge WebDriver safe.

Microsoft Edge WebDriver was started successfully.

その後、WebDriver から Edge を起動し、コントロールするための sessionId を取得しておきます。

$bootBoy = @{
    capabilities = {}
}
$bootResponse = Invoke-RestMethod -Uri "http://localhost:9515/session" -Method POST -body (ConvertTo-Json $bootBoy)
$sessionId    = $bootResponse.value.sessionId

2. Token 取得

まずは、OpenID Connect の構成情報を取得してみましょう。

$tenantId = '11111111'
$config   = Invoke-RestMethod -Method GET -Uri https://auth.worksmobile.com/$tenantId/.well-known/openid-configuration
$config | convertto-json
{                                                            
  "issuer": "https://auth.worksmobile.com",
  "authorization_endpoint": "https://auth.worksmobile.com/oauth2/v2.0/authorize",
  "token_endpoint": "https://auth.worksmobile.com/oauth2/v2.0/token",
  "revocation_endpoint": "https://auth.worksmobile.com/oauth2/v2.0/revoke",
  "jwks_uri": "https://auth.worksmobile.com/oauth2/v2.0/certs/11111111",
  "scopes_supported": [
    "openid",
    "email",
    "profile"
  ],
  "response_types_supported": [
    "code",
    "id_token",
    "token id_token"
  ],
  "grant_types_supported": [
    "authorization_code",
    "implicit",
    "refresh_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post"
  ],
  "claims_supported": [
    "iss",
    "aud",
    "sub",
    "iat",
    "exp",
    "email",
    "email_verified",
    "family_name",
    "given_name",
    "name",
    "locale"
  ]
}

authorization_endpoint に、アプリの情報をもとにしてクエリパラメータを追加して、アクセス URL を作成します。

$redirectUrl  = "https://localhost"
$clientId     = "audaudaud"
$scope        = "openid email profile directory"
$state        = "state"
$responseType = "token id_token"

$b = new-object byte[](24)
(new-object Random).NextBytes($b)
$nonce = [Convert]::ToBase64String($b).Replace('+','-').Replace('/','_')

$url = $config.authorization_endpoint + '?' +
    "client_id="      + [Uri]::EscapeDataString($clientId) +
    "&redirect_uri="  + [Uri]::EscapeDataString($redirectUrl) +
    "&scope="         + [Uri]::EscapeDataString($scope) +
    "&state="         + [Uri]::EscapeDataString($state) +
    "&nonce="         + $nonce +
    "&response_type=" + [Uri]::EscapeDataString($responseType)

URL はこんな形になります。

$url
https://auth.worksmobile.com/oauth2/v2.0/authorize?client_id=audaudaud&redirect_uri=https%3A%2F%2Flocalhost&scope=openid%20email%20profile%20directory&state=state&nonce=nczd5ft3iycWEsyPYFJuNJOnhAnUQ0KD&response_type=token%20id_token

WebDriver からこの URL にアクセスします。認証画面が表示されますので、ログイン情報を入力してユーザー認証をします。
成功すれば、指定した Redirect URL にリダイレクトされ、URL フラグメントに Token その他が返ります。

$authReuestbody = @{
    url = $url
}
Invoke-RestMethod -Uri "http://localhost:9515/session/$sessionId/url" -Method POST -body (ConvertTo-Json $authReuestbody) 

do{
    start-sleep -Seconds 1
    $res = Invoke-RestMethod -Uri "http://localhost:9515/session/$sessionId/url" -Method GET
}while ($false -eq $res.value.startswith($redirectUrl))

参考までに、こんな感じ。

 $res.value
https://localhost/#access_token=jp1AAAA4fw35xWV5T8bZyXSaQ78Py/I5fIGPC1lzmyJGN…

取得できたパラメータの切り出しまでやってみます。

$params = [Uri]::UnescapeDataString($res.value.Split('#')[1])
$paramsCollection = @{}
$paramsArray = $params.Split('&')
foreach($record in $paramsArray)
{
    $idx = $record.IndexOf('=')
    $paramsCollection.add( $record.substring(0,$idx), $record.substring($idx+1))
}

$accessToken_in_response = $paramsCollection["access_token"]
$id_token_in_response    = $paramsCollection["id_token"]
$state_in_response       = $paramsCollection["state"]
$token_type_in_response  = $paramsCollection["token_type"]
$expires_in_in_response  = $paramsCollection["expires_in"]
$scope_in_response       = $paramsCollection["scope"]

切り出した情報はこうなります。

$paramsCollection

Name           Value
----           -----
expires_in     86400
access_token   jp1AAAA4fw35xWV5T8bZyXSaQ78Py/I5fIGPC1lzmyJGNn6
scope          openid email profile directory
token_type     Bearer
state          state
id_token       eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imp

ID Token の Validation

ID Token は、ヘッダー、ペイロード、署名からなる、JSON Web Signature (JWS) で署名された JSON Web Token (JWT) です。
ユーザー情報はペイロードに含まれており簡単に参照することはできますが、取得した ID Token は、Validation をしてから利用する必要があります。

まずは、ID Token から、ヘッダーとペイロードを取得し、エンコードして、中身を確認してみましょう。

function getBytesFromBase64url ([String]$base64url)
{
    $str = $base64url.Replace('-','+').Replace('_','/');
    $mod = $str.Length % 4;
    if ($mod -ne 0){
        $str = $str.padRight($str.Length + (4-$mod), "=")
    }
    return [Convert]::FromBase64String($str)
}

function getStrFromBase64url ([String]$base64url)
{
    $bin = getBytesFromBase64url($base64url);
    return [System.Text.Encoding]::UTF8.GetString( $bin );
}

$token        = $id_token_in_response.split(".")
$tokenHeader  = $token[0]
$tokenPayload = $token[1]
$tokenDigKey  = $token[2]

$tokenHeaderObj  = getStrFromBase64url($tokenHeader) | ConvertFrom-Json
$tokenPayloadObj = getStrFromBase64url($tokenPayload) | ConvertFrom-Json

取得したヘッダーとペイロードはこんな感じです。

$tokenHeaderObj | ConvertTo-Json

{           
  "typ": "JWT",
  "alg": "RS256",
  "kid": "jm64WO526dV5xbL997kleldfljkreodlkjd"
}


$tokenPayloadObj | ConvertTo-Json
{
  "iss": "https://auth.worksmobile.com",
  "sub": "12345678-1234-1234-1234-1234567890ab",
  "aud": "audaudaud",
  "nonce": "nczd5ft3iycWEsyPYFJuNJOnhAnUQ0KD",
  "email": "midori@example.com",
  "name": "枠須みどり",
  "locale": "ja_JP",
  "exp": 1716431137,
  "iat": 1716427537,
  "at_hash": "_aJ7UPKs65m_87U__Uk9Jg",
  "email_verified": "true",
  "family_name": "枠須",
  "given_name": "みどり"
}

iss claim Validation

ペイロードの +iss* claim と、openid-configration の issuer が一致している必要があります。

if (!($tokenPayloadObj.iss.Equals($config.issuer)))
{
    # Not valid 
}

aud claim Validation

ペイロードの aud claim と、アプリのクライアント id $clientId が一致している必要があります。

if (!($tokenPayloadObj.aud.Equals($clientId)))
{
    # Not Valid
}

nonce claim Validation

ペイロードの nonce claim と、認証を要求した際の nonce $nonce が一致している必要があります。

if (!($tokenPayloadObj.nonce.Equals($nonce)))
{
    # Not Valid
}

iat / exp claim Validation

現在の時刻が、iatexp の間である必要があります。

$now = Get-Date
$currentUnixTime = [int](Get-Date($now) -UFormat '%s')
if (($tokenPayloadObj.iat -ge $currentUnixTime) -or ($tokenPayloadObj.exp -le $currentUnixTime))
{
    # Not Valid
}

signature Validation

openid-configration の jwks_uri で公開されている鍵情報から、ID Token のヘッダ情報の kid に一致する公開鍵を参照し、ヘッダとペイロードを署名して、ID Token の署名に一致するか確認します。

まずは、ヘッダ情報の kid に対応する公開鍵を取得します。

$jwks = Invoke-RestMethod -Method GET -Uri $config.jwks_uri 
$key = $jwks.keys | Where-Object{$_.kid -ceq $tokenHeaderObj.kid }
$key
kty : RSA
e   : AQAB
use : sig
kid : jm64WO526dV5xbL997kleldfljkreodlkjd
alg : RS256
n   : jlP8fgD0yyPmF8z6OvXRBXAWu579EIqJZPZHNLZv
      

ヘッダーとペイロードをハッシュ化し、署名情報が正しいか検証します。

using namespace System.Security.Cryptography
$publicKeyParams          = new-object RSAParameters
$publicKeyParams.Modulus  = getBytesFromBase64url($Key.n)
$publicKeyParams.Exponent = getBytesFromBase64url($key.e)
$publicKey                = new-object RSACryptoServiceProvider
$publicKey.ImportParameters($publicKeyParams)
$rsaDeformatter           = new-object RSAPKCS1SignatureDeformatter($publicKey)
$rsaDeformatter.SetHashAlgorithm("SHA256")

$sha256     = [System.Security.Cryptography.SHA256]::Create();
$hash       = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($tokenHeader + "." + $tokenPayload))

$signBinary = getBytesFromBase64url($tokenDigKey)

$isValid = $rsaDeformatter.VerifySignature($hash, $signBinary)

if (!$isValid){
    # Not Valid
}

Validation が済んだ ID Token のペイロード内の情報を参照・利用します。

Access Token Validation

Access Token も、Validation してから利用します。
まず、Access Token を、ID Token ヘッダの alg で指定されたハッシュアルゴリズムでハッシュ化します。
取得したハッシュ値の左側半分を Base64URL Encode し、ID Token ペイロードの at_hash claim に等しいか確認します。

$atHash      = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($accessToken_in_response));
$half_atHash = $atHash[0 .. (($atHash.Length/2)-1)]
$half_atHash_base64url = [Convert]::ToBase64String($half_atHash).Replace('+','-').Replace('/','_').Replace('=','')

if (!($half_atHash_base64url -ceq $tokenPayloadObj.at_hash))
{
    # Not Valid
}

Validation が済んだ Access Token を使用して、Open API へアクセスできます。

0
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
0
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?