AzureAD / microsoft-authentication-library-for-go

The MSAL library for Go is part of the Microsoft identity platform for developers (formerly named Azure AD) v2.0. It enables you to acquire security tokens to call protected APIs. It uses industry standard OAuth2 and OpenID Connect.
MIT License
229 stars 88 forks source link

[Bug] OAuth2 "offline_access" scope expected in granted scopes #416

Open nohns opened 1 year ago

nohns commented 1 year ago

Which version of MSAL Go are you using? Microsoft Authentication Library for Go 1.0.0

Where is the issue?

Is this a new or an existing app? The app is in production and I have upgraded to a new version of Microsoft Authentication Library for Go.

What version of Go are you using (go version)?

$ go version
go version go1.19.3 linux/amd64

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/nohns/.cache/go-build"
GOENV="/home/nohns/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/nohns/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/nohns/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19.3"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build430858566=/tmp/go-build -gno-record-gcc-switches"

Repro

package main

import ( "context" "fmt" "log" "net/http"

"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"

)

// Azure AD app const ( clientID = "" clientSecret = "" tenantID = "" redirectURI = "http://localhost:8080/callback" )

var scopes = []string{"https://outlook.office.com/IMAP.AccessAsUser.All", "offline_access"}

func main() { // Initialize Microsoft confidential client msCred, err := confidential.NewCredFromSecret(clientSecret) if err != nil { log.Fatalf("could not create microsoft cred: %v", err) } app, err := confidential.New(fmt.Sprintf("https://login.microsoftonline.com/%s", tenantID), clientID, msCred) if err != nil { log.Fatalf("could not create microsoft confidential client: %v", err) }

// Handler for oauth2 callback. Exchanges code for token and fails if not successful
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
    code := r.URL.Query().Get("code")
    if code == "" {
        http.Error(w, "code not found", http.StatusBadRequest)
        return
    }

    // Exchange code for token
    _, err := app.AcquireTokenByAuthCode(r.Context(), code, redirectURI, scopes)
    if err != nil {
        msg := fmt.Sprintf("could not exchange code for token: %v", err)
        http.Error(w, msg, http.StatusInternalServerError)
        log.Println(msg)
        return
    }

    // Use token
    fmt.Fprintf(w, "aquired all tokens successfully!")
})

// Print out authorization url for user to visit
authURI, err := app.AuthCodeURL(context.Background(), clientID, redirectURI, scopes)
if err != nil {
    log.Fatalf("could not create auth code url: %v", err)
}
log.Printf("open %s to authenticate...", authURI)

// Serve http server listening for oauth2 callback
http.ListenAndServe(":8080", nil)

}

Expected behavior Expected for token request not to fail because of "offline_access" not being returned (as it SHOULD NOT be per the OAuth2 spec). This is also stated as the expected behavior in this post made by a Microsoft Official on the Q&A site: https://learn.microsoft.com/en-us/answers/questions/806413/scope-offline-access-isnt-being-returned-in-the-to

Actual behavior An error is thrown from MSAL when calling app.AcquireTokenByAuthCode(): 2023/05/07 13:15:59 could not exchange code for token: token response failed because declined scopes are present: offline_access

Possible solution Add a special case for scopes, which is not expected to be returned as granted, instead of failing the flow. Eg. the "offline_access" scope.

Additional context / logs / screenshots Here is link to a repo for reproduction of the issue: https://github.com/nohns/msal-go-offline-access-reproduced

bgavrilMS commented 1 year ago

Hi @tekkamanendless - you don't need to request for offline_access. All MSAL libraries will add this scope, along with profile and openid to the token request. They are required for token caching.

offline_access is a special scope that tells the token issuer to give back a refresh token, which MSAL handles internally, via AcquireTokenSilent method.

Let me know if this doesn't work for you.

bgavrilMS commented 1 year ago

I will leave this open as a supportability issue - we should improve the error message here.

jghiloni commented 1 week ago

This doesn't work with AcquireTokenByDeviceCode -- if I leave off offline_access, I only get an Access Token back, and if I add it, I get the error about offline_access being declined. I don't want my users to have to log in every time, and I don't want to go through the hassle of standing up an ephemeral http server to handle callbacks to do the Auth Code flow.

jghiloni commented 1 week ago

This is on go1.23.2 and MSAL for Go 1.2.2 btw