🐡
Azure Communication Services SMTP Relay で認証問題を解決する方法
メール機能は企業アプリケーションにおいて重要なコンポーネントです。Azure Communication Services (ACS) が提供する SMTP Relay サービスを使えば、メール送信機能を簡単に統合できます。しかし、実装中にいくつかの認証関連の問題に遭遇しました。この記事では、これらの問題を発見して解決するまでの道のりを共有します。
前提条件
始める前に、以下のものが必要です:
- Azure サブスクリプション
- Email Communication Services リソース
- Communication Services リソース
- Microsoft Entra ID アプリケーション
- Communication Services でのロール割り当て(アプリケーションに対する Email Sender ロール)
このロールには以下の権限が必要です:
Microsoft.Communication/CommunicationServices Read
Microsoft.Communication/CommunicationServices Write
Microsoft.Communication/EmailServices Write
最初の課題
最初は Go の標準ライブラリ SMTP クライアント実装から始めました。最初のコードは次のようなものでした:
func main() {
smtpHost := "smtp.azurecomm.net:587"
host, _, err := net.SplitHostPort(smtpHost)
if err != nil {
fmt.Println("Failed to split host and port:", err)
return
}
fmt.Println("host:", host)
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", "From", "DoNotReply@xxxx.azurecomm.net")
e.To = []string{"{to_address}"}
e.Subject = "Test Email"
e.Text = []byte("This is an email sent using github.com/jordan-wright/email.")
// username format: communicationService.applicationId.tenantId
auth := smtp.PlainAuth("", "{username}", "{password}", host)
tlsConfig := &tls.Config{
ServerName: host,
}
err = e.SendWithStartTLS(smtpHost, auth, tlsConfig)
if err != nil {
fmt.Println("Failed to send email:", err)
} else {
fmt.Println("Email sent successfully!")
}
}
しかし、このコードでは次のようなエラーが発生しました:
Failed to authenticate: 504 5.7.4 Unrecognized authentication type
問題の深堀り
問題をより理解するために、OpenSSL を使って SMTP サーバーと直接通信してみました。このアプローチにより、サーバーがどの認証方法をサポートしているかを正確に確認することができました。
注意: Telnet は STARTTLS 暗号化をサポートしていないため、ここでは適していません。
$ openssl s_client -starttls smtp -ign_eof -crlf -connect smtp.azurecomm.net:587
ehlo localhost
250-ic3-transport-smtp-acs-deployment-67d47c5c4c-kdkxc Hello [14.19.xx.xx]
250-SIZE 31457280
250 AUTH LOGIN
サーバーの応答から重要なことが判明しました:ACS SMTP サーバーは AUTH LOGIN
認証のみをサポートしています。AUTH PLAIN
を試すと:
AUTH PLAIN
504 5.7.4 Unrecognized authentication type
これで最初のエラーの原因が説明できました - Go の smtp.PlainAuth
は AUTH PLAIN
を使用しますが、これはサーバーでサポートされていないのです。
解決策
根本原因を理解したうえで、AUTH LOGIN
をサポートするカスタム認証機能を実装しました:
type loginAuth struct {
username string
password string
}
// LoginAuth implements smtp.Auth interface
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, fmt.Errorf("unknown fromServer: %s", string(fromServer))
}
}
return nil, nil
}
完全な実装は次のようになります:
func main() {
smtpHost := "smtp.azurecomm.net:587"
host, _, err := net.SplitHostPort(smtpHost)
if err != nil {
fmt.Println("Failed to split host and port:", err)
return
}
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", "From", "DoNotReply@xxxx.azurecomm.net")
e.To = []string{"{to_address}"}
e.Subject = "Test Email"
e.Text = []byte("This is an email sent using github.com/jordan-wright/email.")
auth := LoginAuth(
// username format: communicationService.applicationId.tenantId
"{username}",
"{password}",
)
tlsConfig := &tls.Config{
ServerName: host,
}
err = e.SendWithStartTLS(smtpHost, auth, tlsConfig)
if err != nil {
fmt.Println("Failed to send email:", err)
} else {
fmt.Println("Email sent successfully!")
}
}
重要なポイント
- Azure Communication Services SMTP は
AUTH LOGIN
認証のみをサポートしています - Go の標準ライブラリは
AUTH LOGIN
を標準でサポートしていません - SMTP の問題をトラブルシューティングする際、OpenSSL はサーバーとの直接通信に非常に価値のあるツールです
- メールサービスを統合する際は、常にサポートされている認証方法を確認しましょう
参考リソース
この記事が役に立ったと思われたら、いいねとフォローをお願いします。クラウドサービスと Go プログラミングに関するより多くの技術コンテンツをお届けします。
Discussion