LoginSignup
0
1

More than 3 years have passed since last update.

GoでAzureADのOpenIDConnectを使う

Posted at

OpenIDConnectを使う場合

  • oauth2ライブラリでも同様のコードというかフローで認証認可できる
  • AzureADでは、oauth2認証リクエストにScopeにoidc.ScopeOpenID があればOpenIDConnect扱いで認証される模様
  • このScopeがない場合は、認証はできるけれど、メアドなどでのフィルタリングを行う条件付きアクセス(Azure AD Premium)が利用できないみたい

リポジトリ

AzureAD

  • リダイレクトURLをhttp://localhost:5556/auth/callbackにしておく image.png

コード

package main

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/coreos/go-oidc/v3/oidc"
    "golang.org/x/net/context"
    "golang.org/x/oauth2"
)

var (
    clientID     = "1d46723d-6395-4526-ae88-028d8b5013e8"
    clientSecret = "<secretkey>"
)

func main() {
    // 空のコンテキストを生成
    // よくわからないけれどリクエストを待ってくれたりとかそういう感じだと思う
    ctx := context.Background()

    provider, err := oidc.NewProvider(ctx, "https://login.microsoftonline.com/<tenantid>/v2.0")
    if err != nil {
        log.Fatal(err)
    }
    oidcConfig := &oidc.Config{
        ClientID: clientID,
    }
    verifier := provider.Verifier(oidcConfig)

    config := oauth2.Config{
        ClientID:     clientID,
        ClientSecret: clientSecret,
        Endpoint:     provider.Endpoint(),
        RedirectURL:  "http://localhost:5556/auth/callback",
        Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
    }

    state := "foobar" // Don't do this in production.

    // http://127.0.0.1:5556 へアクセスしたら次のAuthCodeURLへリダイレクトされる関数
    // https://login.microsoftonline.com/66674ed0-005d-4b5a-9138-2a7725b9df89/oauth2/v2.0/authorize?client_id=1d46723d-6395-4526-ae88-028d8b5013e8&redirect_uri=http%3A%2F%2Flocalhost%3A5556%2Fauth%2Fcallback&response_type=code&scope=openid+profile+email&state=foobar
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
    })

    // AzureADで認証後、リダイレクトされるURL(http://127.0.0.1:5556/auth/callback)へアクセスすると実行される関数
    // 返ってくるURLでcodeの部分は10分程で効力がなくなる
    // http://localhost:5556/auth/callback?code=0.AAAA0E5nZl0AWkuROCp3JbnfiT1yRh2VYyZFrogCjYtQE-hJAKc.AQABAAIAAAD--DLA3VO7QrddgJg7WevryGUp832HBkuxup_wVuG322QHt-CB9lCu2aRwvsTOaweCmt3hSSqHa1qzeeDCUHsAEKvkxrHZsfrLNIvfqMoJRGS12Dgzg3-OaIjOVGl0KtIcu14TLFCRrb7QVQ81uji4qPExekYRLHMhn5auAbvCqr_E5zAgyeRCvilfMp175fEkvoaoeFVBlv0JHgCcF4PvdDVdgKpKX_i1-vvjqr6bq2EzFtnHQHWDyp1Uz6kf2_rz4sgXAdTF5fyO399ecP5dBSwlIpdn7tErVQv3Egfp4XBLm8A6542zEjGhRbyK8TyV7zjttENkdcBjMoGu4vfo6YrISDBhlO3tbL3BYaMNpDoJv_e7aahBPl2bGUEzj6gRxg3b5NlOUNSgQqtsaBOYkONGm8nNhp4nseVGNLNapmZUSm4swnqxzLlHPp1VBgwq0xgjWOEgWSl8YjTeAyaOXEn6GljqDCvX4U0J2Rm1e-ciDyY_L-LDo-1rf_S4YS-QInl86G7eft0WrsDAq2ruwDvK1aDbLtAxEjpDcHwhyB2igifeLiJ32asZRCxp1m8SHTnDQ_7VzOjiHPSzIMo5tlGAvpEGWQzPdx-qYiBYzHfGrRR2SSTbCNdTztRvMzMgAA&state=foobar&session_state=21823950-de1b-469b-9589-8c06d6c2d97c#
    http.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) {

        // stateが"foobar"でなかったらエラー扱い
        log.Printf(r.URL.Query().Get("state"))
        if r.URL.Query().Get("state") != state {
            http.Error(w, "state did not match", http.StatusBadRequest)
            return
        }

        // config.Exchangeはcodeからtokenを取得する関数
        // xxxへcode(認証コード)を送信してtokenを取得できなかったらエラー扱い
        oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
        if err != nil {
            http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
            return
        }

        // tokenを文字列型で取得しrawIDTokenへ入れる
        rawIDToken, ok := oauth2Token.Extra("id_token").(string)
        if !ok {
            http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
            return
        }

        // idtokenの検証(AzureADで検証しているわけではなくtokenの構造なのかをチェック文字数とか中身のデータとか)
        idToken, err := verifier.Verify(ctx, rawIDToken)
        if err != nil {
            http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
            return
        }

        // idtokenを削除 一度きりのアクセス
        oauth2Token.AccessToken = "*REDACTED*"

        resp := struct {
            OAuth2Token   *oauth2.Token
            IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
        }{oauth2Token, new(json.RawMessage)}

        if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        data, err := json.MarshalIndent(resp, "", "    ")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        w.Write(data)
    })

    log.Printf("listening on http://%s/", "127.0.0.1:5556")
    log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}
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