Skip to content

Instantly share code, notes, and snippets.

@c-bata
Created September 28, 2017 10:05
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save c-bata/488c4dbfae807ad113c07195e7388e09 to your computer and use it in GitHub Desktop.
Save c-bata/488c4dbfae807ad113c07195e7388e09 to your computer and use it in GitHub Desktop.
Verify the JSON Web Token obtained from Firebase Authentication. But now, Go SDK is released by firebase organization.
package jwt
import (
"crypto/rsa"
"errors"
"encoding/json"
"io/ioutil"
"net/http"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/dgrijalva/jwt-go"
)
// CertsAPIEndpoint is endpoint of getting Public Key.
var CertsAPIEndpoint = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
// GetCertificate is useful for testing.
var GetCertificate = getCertificate
func getCertificates() (certs map[string]string, err error) {
res, err := http.Get(CertsAPIEndpoint)
if err != nil {
return
}
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
json.Unmarshal(data, &certs)
return
}
// GetCertificate returns certificate.
func getCertificate(kid string) (cert []byte, err error) {
certs, err := getCertificates()
if err != nil {
return
}
certString := certs[kid]
cert = []byte(certString)
err = nil
return
}
// VerifyJWT to verify the token header, payload and signature.
var VerifyJWT = verifyJWT
// GetCertificateFromToken returns cert from token.
func GetCertificateFromToken(token *jwt.Token) ([]byte, error) {
// Get kid
kid, ok := token.Header["kid"]
if !ok {
return []byte{}, errors.New("kid not found")
}
kidString, ok := kid.(string)
if !ok {
return []byte{}, errors.New("kid cast error to string")
}
return GetCertificate(kidString)
}
// Verify the token payload.
func verifyPayload(t *jwt.Token, projectID string) (ok bool, uid string) {
claims, ok := t.Claims.(jwt.MapClaims)
if !ok {
return
}
// Verify User
claimsAud, ok := claims["aud"].(string)
if claimsAud != projectID || !ok {
return
}
// Verify issued at
iss := "https://securetoken.google.com/" + projectID
claimsIss, ok := claims["iss"].(string)
if claimsIss != iss || !ok {
return
}
// sub is uid of user.
uid, ok = claims["sub"].(string)
if !ok {
return
}
return
}
func readPublicKey(cert []byte) (*rsa.PublicKey, error) {
publicKeyBlock, _ := pem.Decode(cert)
if publicKeyBlock == nil {
return nil, errors.New("invalid public key data")
}
if publicKeyBlock.Type != "CERTIFICATE" {
return nil, fmt.Errorf("invalid public key type: %s", publicKeyBlock.Type)
}
c, err := x509.ParseCertificate(publicKeyBlock.Bytes)
if err != nil {
return nil, err
}
publicKey, ok := c.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, errors.New("not RSA public key")
}
return publicKey, nil
}
func verifyJWT(t, projectID string) (uid string, ok bool) {
parsed, _ := jwt.Parse(t, func(t *jwt.Token) (interface{}, error) {
cert, err := GetCertificateFromToken(t)
if err != nil {
return "", err
}
publicKey, err := readPublicKey(cert)
if err != nil {
return "", err
}
return publicKey, nil
})
ok = parsed.Valid
if !ok {
return
}
// Verify header.
if parsed.Header["alg"] != "RS256" {
ok = false
return
}
// Verify payload.
ok, uid = verifyPayload(parsed, projectID)
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment