From 3be2995349d72b605b53af457f9abe5cde67333c Mon Sep 17 00:00:00 2001 From: DaysJan Date: Fri, 18 Oct 2024 12:34:22 +0800 Subject: [PATCH] feat: Implement "Verify ID Token" https://developers.line.biz/en/reference/line-login/#verify-id-token --- response.go | 27 +++++++++++++++++++++++ social.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ social_test.go | 21 ++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/response.go b/response.go index dac4c97..e2adaaa 100644 --- a/response.go +++ b/response.go @@ -76,6 +76,21 @@ type TokenRefreshResponse struct { RefreshToken string `json:"refresh_token"` } +// VerifyIDTokenResponse type +type VerifyIDTokenResponse struct { + Iss string `json:"iss"` + Sub string `json:"sub"` + Aud string `json:"aud"` + Exp int `json:"exp"` + Iat int `json:"iat"` + AuthTime int `json:"auth_time"` + Nonce string `json:"nonce"` + Amr []string `json:"amr"` + Name string `json:"name"` + Picture string `json:"picture"` + Email string `json:"email"` +} + // GetUserProfileResponse type type GetUserProfileResponse struct { // UserID: Identifier of the user @@ -239,6 +254,18 @@ func decodeToTokenRefreshResponse(res *http.Response) (*TokenRefreshResponse, er return &result, nil } +func decodeToVerifyIDTokenResponse(res *http.Response) (*VerifyIDTokenResponse, error) { + if err := checkResponse(res); err != nil { + return nil, err + } + decoder := json.NewDecoder(res.Body) + result := VerifyIDTokenResponse{} + if err := decoder.Decode(&result); err != nil { + return nil, err + } + return &result, nil +} + func decodeToGetUserProfileResponse(res *http.Response) (*GetUserProfileResponse, error) { if err := checkResponse(res); err != nil { return nil, err diff --git a/social.go b/social.go index 8ac5ff1..f7cbb86 100644 --- a/social.go +++ b/social.go @@ -278,6 +278,65 @@ func (call *RevokeTokenCall) Do() (*BasicResponse, error) { return decodeToBasicResponse(res) } +// VerifyIDToken ID tokens are JSON web tokens (JWT) with information about the +// user. It's possible for an attacker to spoof an ID token. Use this call to +// verify that a received ID token is authentic, meaning you can use it to obtain +// the user's profile information and email. +// https://developers.line.biz/en/reference/line-login/#verify-id-token +func (client *Client) VerifyIDToken(iDToken string, options VerifyIDTokenRequestOptions) *VerifyIDTokenCall { + return &VerifyIDTokenCall{ + c: client, + iDToken: iDToken, + options: options, + } +} + +type VerifyIDTokenRequestOptions struct { + nonce string + userID string +} + +// VerifyIDTokenCall type +type VerifyIDTokenCall struct { + c *Client + ctx context.Context + + iDToken string + options VerifyIDTokenRequestOptions +} + +// WithContext method +func (call *VerifyIDTokenCall) WithContext(ctx context.Context) *VerifyIDTokenCall { + call.ctx = ctx + return call +} + +// Do method +func (call *VerifyIDTokenCall) Do() (*VerifyIDTokenResponse, error) { + data := url.Values{} + data.Set("id_token", call.iDToken) + data.Set("client_id", call.c.channelID) + + if call.options.nonce != "" { + data.Set("nonce", call.options.nonce) + } + + if call.options.userID != "" { + data.Set("user_id", call.options.userID) + } + + res, err := call.c.post(call.ctx, APIEndpointTokenVerify, strings.NewReader(data.Encode())) + if res != nil && res.Body != nil { + defer res.Body.Close() + } + + if err != nil { + return nil, err + } + + return decodeToVerifyIDTokenResponse(res) +} + // GetUserProfile: Gets a user's display name, profile image, and status message. //Note: Requires an access token with the profile scope. For more information, see Making an authorization request and Scopes. func (client *Client) GetUserProfile(accessToken string) *GetUserProfileCall { diff --git a/social_test.go b/social_test.go index 574fe99..25c3644 100644 --- a/social_test.go +++ b/social_test.go @@ -9,20 +9,24 @@ import ( var ( accessToken string refreshToken string + iDToken string cID string cSecret string qURL string code string + userID string ) //Provide those data for testing. func init() { accessToken = os.Getenv("LINE_ACCESS_TOKEN") refreshToken = os.Getenv("LINE_REFRESH_TOKEN") + iDToken = os.Getenv("LINE_ID_TOKEN") cID = os.Getenv("LINE_CLIENT_ID") cSecret = os.Getenv("LINE_CLIENT_SECRET") qURL = os.Getenv("LINE_SERVER_URL") code = os.Getenv("LINE_LOGIN_CODE") + userID = os.Getenv("LINE_USER_ID") } func checkEnvVariables(t *testing.T) { @@ -123,6 +127,23 @@ func TestRevokeToken(t *testing.T) { log.Println("ret:", ret) } +func TestVerifyIDToken(t *testing.T) { + checkEnvVariables(t) + + nonce := GenerateNonce() + + client, _ := New(cID, cSecret) + ret, err := client.VerifyIDToken(iDToken, VerifyIDTokenRequestOptions{ + nonce: nonce, + userID: userID, + }).Do() + if err != nil { + log.Println("err:", err) + } + + log.Println("ret:", ret) +} + func TestGetAccessTokenPKCE(t *testing.T) { checkEnvVariables(t)