firebase / firebase-admin-go

Firebase Admin Go SDK
Apache License 2.0
1.13k stars 244 forks source link

Supplying service account creds with option.WithCredentials() doesn't throw an error until user tries to verify a token #429

Closed andrewmathies closed 3 years ago

andrewmathies commented 3 years ago

[REQUIRED] Step 2: Describe your environment

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

I just spent several days confused as to why my backend server couldn't verify tokens. My client is a Unity game, and my server is written in Go.

I was trying to provide the service account credentials in a way that apparently isn't supported. I plan on running this backend service in a container and I wanted to make sure I didn't compromise my service account credentials. So instead of keeping the credentials in a file I decided to download them in a secure way and supply them to firebase.NewApp() by creating an option via WithCredentials().

I incorrectly assumed this would be a valid way to provide the credentials, in part because the docs show that providing service account credentials via WithCredentialsFile() works fine.

The error message I was getting completely threw me off the trail and it seemed like I couldn't find anyone else that had experienced the same issue when I searched online. The error message I kept seeing was:

Get "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com": oauth2: cannot fetch token: 400 Bad Request Response: {"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}

Relevant Code:

// authenticate the service account
credsOption := option.WithCredentials(creds)
app, initErr := firebase.NewApp(context.Background(), nil, credsOption)
client, authErr := app.Auth()
...

// verify the token
token, verifyErr := client.VerifyIDToken(idToken)

Goal

I'm raising this issue because I think it would be nice if the docs had a note saying you cannot use WithCredentials() to provide service account credentials. Perhaps in this snippet?

Alternatively, if the SDK raised an error when someone tried to provide credentials the wrong way, or at least printed a better error message that clearly explained what the user is doing incorrectly that would be good too.

I just don't want anyone to waste as much time as I did trying to troubleshoot this problem.

hiranya911 commented 3 years ago

That's strange. The SDK shouldn't even attempt to use credentials inVerifyIDToken(). We explicitly use an unauthenticated HTTP client to connect to https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

https://github.com/firebase/firebase-admin-go/blob/eb0d2a08b9f4347c9791ae75ca6bbc226a676a08/auth/token_verifier.go#L106-L124

hiranya911 commented 3 years ago

I just tried the following and it ran to end with no issues:

func main() {
    ctx := context.Background()

    b, err := ioutil.ReadFile(path)
    if err != nil {
        log.Fatalln(err)
    }

    creds, err := google.CredentialsFromJSON(ctx, b)
    if err != nil {
        log.Fatalln(err)
    }

    app, err := firebase.NewApp(ctx, nil, option.WithCredentials(creds))
    if err != nil {
        log.Fatalln(err)
    }

    auth, err := app.Auth(ctx)
    if err != nil {
        log.Fatalln(err)
    }

    decoded, err := auth.VerifyIDToken(ctx, token)
    if err != nil {
        log.Fatalln(err)
    }

    log.Println(decoded.UID)
}

Are you using an older version of the SDK by any chance? Really old versions of the SDK used an authenticated client to fetch public keys, and that's one way you can run into the above problem.

andrewmathies commented 3 years ago

Yeah it looks like I've been using v1.02 instead of v4. I guess I didn't read https://firebase.google.com/docs/admin/migrate-go-v4 close enough. I think by running go get firebase.google.com/go with an existing .mod file I accidentally installed v1 of the library

Whoops :sweat_smile: