Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify ID token? #160

Closed
nishidanagisa opened this issue Aug 23, 2016 · 35 comments
Closed

Verify ID token? #160

nishidanagisa opened this issue Aug 23, 2016 · 35 comments
Assignees
Labels
🚨 This issue needs some love. triage me I really want to be triaged.

Comments

@nishidanagisa
Copy link

How can I verify the ID token (JWT) using this library?

@bradfitz
Copy link
Contributor

There doesn't seem to be enough information in this bug report to help, or I don't follow.

@mcgreevy @okdave, is there a user mailing list for questions like this?

@mcgreevy
Copy link
Contributor

No, there isn't a mailing list.

@nishidanagisa please provide more details.

@nishidanagisa
Copy link
Author

Please see Verify the integrity of the ID token
where the client libraries are referred to as the "recommended way to validate Google ID tokens in a production environment."

@mcgreevy
Copy link
Contributor

We don't provide this as part of the google-api-go-client libraries. @rakyll may have a suggestion for an alternative.

@rakyll
Copy link
Contributor

rakyll commented Aug 26, 2016

https://godoc.org/golang.org/x/oauth2/jws#Verify should be able to verify ID tokens.

@nishidanagisa
Copy link
Author

Thanks.

@cloudlena
Copy link

In the docs for https://godoc.org/golang.org/x/oauth2/jws it says that the package is deprecated... Is there any alternative package to be used? I saw that in most languages' Google API clients the verify functionality is built in...

@apenwarr
Copy link

I just ran into this problem myself. Here's an example of validating a google oauth2 id_token value from an HTTP request that passes u.getAuthResponse().id_token as a POST parameter named 'token':

ctx := r.Context()
token := r.PostFormValue("token")
svc, err := oauth2.New(http.DefaultClient)
ti, err := svc.Tokeninfo().IdToken(token).Context(ctx).Do()
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}
log.Printf("tokeninfo: %#v", ti)
if ti.Audience != CLIENT_ID {
	http.Error(w, "tokeninfo: wrong client id", http.StatusInternalServerError)
}
if !ti.VerifiedEmail {
	http.Error(w, "tokeninfo: email address not verified", http.StatusInternalServerError)
}

@apenwarr
Copy link

Note however that this requires an extra RPC just to validate the token. A properly local JWS decode would be better, but I don't know where to get the public key from, and as noted above, it would require using the internal-only JWS library.

@broady
Copy link
Contributor

broady commented Nov 17, 2017

Funny that you brought this up just now. Just today, I was thinking about working on a nice interface to verify tokens. It would look up the public key for the given issuer, caching it, so only one roundtrip is required for each key across all JWTs that you want to verify -- usually just one or a few, because you know which auth providers you allow.

Google's certs are here:
https://www.googleapis.com/oauth2/v3/certs (jwk)
or here https://www.googleapis.com/oauth2/v1/certs (pem)

jws.Verify works well, but doesn't bridge that gap of actually getting the certs you want (and of course, validating that they match the issuer field).

(btw, big fan of sshuttle!)

@salrashid123
Copy link

just for ref, google-auth python provides some functionality:

@mbakkokom
Copy link

mbakkokom commented Apr 21, 2018

So the Tokeninfo does not actually verify the id_token?

Is it fine to use other libs like https://github.com/futurenda/google-auth-id-token-verifier to verify the id_token?

@artem-v-shamsutdinov
Copy link

artem-v-shamsutdinov commented Oct 19, 2018

Thank you for the link to 3rd party lib! Works like a charm. I copied it's source and am customizing it to fit my app's needs.

@yoshi-automation yoshi-automation added triage me I really want to be triaged. 🚨 This issue needs some love. labels Apr 6, 2020
@preom
Copy link

preom commented May 3, 2020

Funny that you brought this up just now. Just today, I was thinking about working on a nice interface to verify tokens. It would look up the public key for the given issuer, caching it, so only one roundtrip is required for each key across all JWTs that you want to verify -- usually just one or a few, because you know which auth providers you allow.

Google's certs are here:
https://www.googleapis.com/oauth2/v3/certs (jwk)
or here https://www.googleapis.com/oauth2/v1/certs (pem)

jws.Verify works well, but doesn't bridge that gap of actually getting the certs you want (and of course, validating that they match the issuer field).

(btw, big fan of sshuttle!)

@broady Any update on something like this? I've been tearing my hair out over this over the past few hours because other solutions (using 3rd party lib, using another language, or making a request to an endpoint vs. just verifying the jwt token) all seem subpar. This really feels like something that should be part of the official library. Ideally, one that better handles caching the certs.

It kind of seems like the idtoken package has a working solution, but I am having a hard time getting it to work with go modules.

@MaciejKaras
Copy link

@preom this import works for me:
import "google.golang.org/api/idtoken"

However it does not verify token expiration, issuedAt etc. It only gives you signed payload.

I think the best way would be to use fork of https://github.com/futurenda/google-auth-id-token-verifier.

@preom
Copy link

preom commented May 5, 2020

@MaciejKaras Ah yes, that does seem to work. I apologize for that. I can only blame it on tiredness and the fact that vscode erases failed imports even if the package is used in the code (idk why I didn't use a blank identifier which would've stopped the erasure by gofmt).

You're right, in the end that does seem to be the best solution (i'd already forked it yesterday). I'll leave a note to maybe come back to this if ever we decide to tackle reducing our dependencies.

Thank you for your help.

@agusterodin
Copy link

As mentioned by MaciejKaras https://github.com/googleapis/google-api-go-client/tree/v0.24.0/idtoken contains both idtoken.go and validate.go.

Validate appears to have everything you need as far as checking your jwt signed payload against google's public key. Expiry date and audience should be trivial and you can check them yourself :D

Seems like the best long term approach since the code is direct from google, google's code is frequently updated, and futurenda's library hasn't been touched for 3 years and relies on a deprecated dependency (futurenda/google-auth-id-token-verifier#3).

I am about to code an id retrieval function myself using this (google's) library. Let me know if you have any example code, otherwise I will share mine once finished.

@agusterodin
Copy link

agusterodin commented May 17, 2020

The api is not as straightforward as I would have thought. Has anybody gotten token retrieval using https://github.com/googleapis/google-api-go-client working?

@broady
Copy link
Contributor

broady commented May 17, 2020 via email

@agusterodin
Copy link

I wasn't trying to use this library to actually use google servers, just use google sign-in for login federation. I ended up just sending the jwt from client side google sign in to my backend instead of relying on the whole authCode -> user information payload exchange method. The React google sign in library was set in "authCode" mode instead of "idToken" aka JWT mode and I didn't realize I could change this setting.

This is probably way off topic but for anybody interested, here is how I validate the google JWT on my backend using a very popular Golang JWT library (https://github.com/dgrijalva/jwt-go):

authentication_repository.go

package repository

import (
	"crypto/rsa"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"net/http"

	"github.com/agusterodin/pixelboard/datamodels"
	"github.com/dgrijalva/jwt-go"
)

var (
	ErrorPublicKeyUnavailable = errors.New("public key couldn't be retrieved")
	ErrorInvalidToken         = errors.New("token couldn't be parsed or validated")
	ErrorInvalidIssuer        = errors.New("incorrect issuer claim")
)

func retrievePublicKey() (datamodels.JSONWebKeys, error) {
	resp, err := http.Get("https://www.googleapis.com/oauth2/v3/certs")
	if err != nil {
		return datamodels.JSONWebKeys{}, err
	}
	defer resp.Body.Close()
	var jwks = datamodels.JSONWebKeys{}
	err = json.NewDecoder(resp.Body).Decode(&jwks)
	if err != nil {
		return datamodels.JSONWebKeys{}, err
	}
	return jwks, nil
}

func getPublicKey(jwks datamodels.JSONWebKeys) func(token *jwt.Token) (interface{}, error) {
	return func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != jwt.SigningMethodRS256.Alg() {
			return nil, fmt.Errorf("unexpected JWT signing method=%v", token.Header["alg"])
		}
		for key := range jwks.Keys {
			if token.Header["kid"].(string) == jwks.Keys[key].Kid {
				n, err := base64.RawURLEncoding.DecodeString(jwks.Keys[key].N)
				if err != nil {
					return nil, err
				}
				e, err := base64.RawURLEncoding.DecodeString(jwks.Keys[key].E)
				if err != nil {
					return nil, err
				}
				ei := big.NewInt(0).SetBytes(e).Int64()
				if err != nil {
					return nil, err
				}
				return &rsa.PublicKey{
					N: big.NewInt(0).SetBytes(n),
					E: int(ei),
				}, nil
			}
		}
		return nil, nil
	}
}

func GetIdentityFromToken(token string) (*datamodels.GoogleTokenClaims error) {
	jwks, err := retrievePublicKey()
	if err != nil {
		return nil, ErrorPublicKeyUnavailable
	}
	parsedToken := new(jwt.Token)
	parsedToken, err = jwt.ParseWithClaims(token, &datamodels.GoogleTokenClaims{}, GetPublicKey(jwks))
	if err != nil {
		return nil, ErrorInvalidToken
	}
	claims := parsedToken.Claims.(*datamodels.GoogleTokenClaims)
	isValidIssuer := claims.VerifyIssuer("accounts.google.com", true)
	if !isValidIssuer {
		return nil, ErrorInvalidIssuer
	}
	return claims, nil
}

authentication_datamodels.go

package datamodels

import "github.com/dgrijalva/jwt-go"

type JSONWebKeys struct {
	Keys []JSONWebKey `json:"keys"`
}

type JSONWebKey struct {
	Kty string `json:"kty"`
	Kid string `json:"kid"`
	E   string `json:"e"`
	Alg string `json:"alg"`
	Use string `json:"use"`
	N   string `json:"n"`
}

type GoogleTokenClaims struct {
	Name          string `json:"name"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`
	jwt.StandardClaims
}

Hope this helps someone.

@agusterodin
Copy link

If I wasted time doing this and there is an easier way to validate my token and get the claims from it by using this library (but not actually using it to hit other google services, just strictly to validate and extract jwt claims), please hook a brother up with some example code.

@MaciejKaras
Copy link

MaciejKaras commented May 18, 2020

@agusterodin Maybe something like this works for you?

It only calls google cert endpoint under the hood (idtoken library calls cert endpoint being precise), nothing else.

https://gist.github.com/MaciejKaras/1f867a1cedd4500b6304085b66f5f5df

@broady
Copy link
Contributor

broady commented May 18, 2020

Both your use cases should be covered by the one liner:

payload, err := idtoken.Verify(ctx, "eyJhbGciOiJSUzI1NiI...", "1234.apps.googleusercontent.com")
if err != nil {
   // Not a Google ID token.
}
log.Print(payload.Claims)

Would a docs change have made that clearer? An example in the docs?
Was it unclear what kind of validation is performed?

@agusterodin
Copy link

agusterodin commented May 18, 2020

Both your use cases should be covered by the one liner:

payload, err := idtoken.Verify(ctx, "eyJhbGciOiJSUzI1NiI...", "1234.apps.googleusercontent.com")
if err != nil {
   // Not a Google ID token.
}
log.Print(payload.Claims)

Would a docs change have made that clearer? An example in the docs?
Was it unclear what kind of validation is performed?

Thanks for this! A lot simpler than everything I did. I could not find this sort of example code anywhere. And godocs always take a lot of digging to find what you need.

I tried the code and it mostly works. It is able to properly detect an invalid signature when I intentionally change the signature to an incorrect one and is able to properly detect when audience is wrong and display error.

Unfortunately the log.Print statement doesn't show any payload stuff when there are no errors. It appears to just be a blank map based on the print statement. I used the exact code you provided and provided context.Background() as the first argument. Is there anything else I need to do to get access to the claims?

Also, I think you were referring to the "Validate" function. I couldn't find Verify in this package in godocs.

@broady
Copy link
Contributor

broady commented May 18, 2020

Right, sorry, it's a new library and we haven't included it in our docs yet. You're one of the first users.

Yes, the idea is that you don't get the payload if validation failed.

Also, I think you were referring to the "Validate" function. I couldn't find Verify in this package in godocs.

I am. Sorry, I used them interchangeably.

@agusterodin
Copy link

agusterodin commented May 18, 2020

I tried oauth2.NoContext for the first argument as well and I still can't get the payload. It appears to be an empty map even when I use spew.Dump(payload.Claims) to try to investigate contents. No errors are thrown and I am 100% sure that this is a valid google jwt. I am able to see contents in jwt.io with no issues as well.

Is there anything in the code that needs to come before the snippet you just posted?

@agusterodin
Copy link

It would have been more clear what the function was if it was called "ParseAndValidate" or something like that

@agusterodin
Copy link

All the other information is present as well. Just not the claims.

image

@broady
Copy link
Contributor

broady commented May 18, 2020

Hm, that looks like a bug, then. Could you file that? /cc @codyoss

@keyslapperdev
Copy link

I'm having problems using the idtoken package to validate the JWT Token provided by google for a chat bot implementation I've created. After a few days of digging, the problem seems to be related to the ones in this thread, but in a different way. Although, it's likely that I'm using the wrong library altogether.

The current documentation for Google Chat bot verification shows

All bearer tokens sent with requests from Google chat will have chat@system.gserviceaccount.com as the issuee, with the audience field specifying the target bot's project number from the Google API Console. For example, if the request is for a bot with the project number 1234567890, then the audience is 1234567890.

In their code examples they show that the url to get the keys for validation should be https://www.googleapis.com/service_accounts/v1/metadata/x509/ + ${theAboveIsuee}

In the source for idtoken, the url used is https://www.googleapis.com/oauth2/v3/certs

Based on that difference, I believe I may be using the wrong library, but I'm not sure which library I should be using for this.

Would anyone be able to offer some guidance here?

@salrashid123
Copy link

@name-schema i dont' think google.golang.org/api/idtoken will work here since it just looks to validate against google OIDC and IAP JWK urls.

this library isn't a geric JWT validator but others like google auth python can validate servcie account signed JWTs. For example, in google-auth python, you can specify the JWK URL endpoint (certs_url=.

i'd recommend one of the two libraries here (i use the first set)

"github.com/dgrijalva/jwt-go" with "github.com/lestrrat/go-jwx/jwk"
or
"github.com/coreos/go-oidc"

@codyoss
Copy link
Member

codyoss commented Aug 25, 2020

@name-schema Could you file a feature request issue for this?

@keyslapperdev
Copy link

@salrashid123 Thank you very much for the guidance, I'll try and complete the task using what you've provided. Just kinda bummed because I thought the google-api-go-client would have something to validate against it's own tokens, but I guess that's neophic thinking.

@codyoss CERTAINLY!! I'll figure out how to do that, and get it done asap.

@daveneeley
Copy link

Chat feature request in #640

@ghost
Copy link

ghost commented Apr 15, 2021

Both your use cases should be covered by the one liner:

payload, err := idtoken.Verify(ctx, "eyJhbGciOiJSUzI1NiI...", "1234.apps.googleusercontent.com")
if err != nil {
   // Not a Google ID token.
}
log.Print(payload.Claims)

Would a docs change have made that clearer? An example in the docs?
Was it unclear what kind of validation is performed?

You deserves a medal. You saved my day I was stuck in here for 2 hour. This works perfectly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🚨 This issue needs some love. triage me I really want to be triaged.
Projects
None yet
Development

No branches or pull requests