func authToken(oidc *Client, jwt jose.JWT, mustBeAdmin bool) (string, bool, error) { claims, err := jwt.Claims() if err != nil { return "", false, fmt.Errorf("auth.go: failed to get claims %v", err) } aud, ok, err := claims.StringClaim("aud") if err != nil || !ok || aud == "" { return "", false, fmt.Errorf("auth.go: failed to parse 'aud' claim: %v", err) } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", false, fmt.Errorf("auth.go: failed to parse 'sub' claim: %v", err) } if !ok || sub == "" { return "", false, fmt.Errorf("auth.go: missing required 'sub' claim") } err = oidc.VerifyJWT(jwt, aud) if err != nil { return sub, false, fmt.Errorf("auth.go: Failed to verify signature: %v", err) } typ, _, _ := claims.StringClaim(OtsimoUserTypeClaim) if mustBeAdmin { if typ != OtsimoAdminUserType { return sub, false, fmt.Errorf("auth.go: user must be admin") } } return sub, typ == OtsimoAdminUserType, nil }
func getUserFromJWT(jwt jose.JWT) (*models.User, error) { claims, err := jwt.Claims() if err != nil { return nil, fmt.Errorf("auth.go: failed to get claims %v", err) } sub, ok, err := claims.StringClaim("sub") if err != nil { return nil, fmt.Errorf("auth.go: failed to parse 'sub' claim: %v", err) } if !ok || sub == "" { return nil, fmt.Errorf("auth.go: missing required 'sub' claim") } aud, _, _ := claims.StringClaim("aud") typ, _, _ := claims.StringClaim(OtsimoUserTypeClaim) isadmin := typ == OtsimoAdminUserType return &models.User{ ID: sub, IsAdmin: isadmin, Token: jwt, ClientID: aud, }, nil }
func compareJWTs(a, b jose.JWT) string { if a.Encode() == b.Encode() { return "" } var aClaims, bClaims jose.Claims for _, j := range []struct { claims *jose.Claims jwt jose.JWT }{ {&aClaims, a}, {&bClaims, b}, } { var err error *j.claims, err = j.jwt.Claims() if err != nil { *j.claims = jose.Claims(map[string]interface{}{ "msg": "bad claims", "err": err, }) } } return diff.ObjectDiff(aClaims, bClaims) }
// Extracts the JWT Identity Claims (includes ID, Email, Expiry) from a JWT. func GetJWTIdentity(jwt jose.JWT) (identity *oidc.Identity, err error) { claims, err := jwt.Claims() if err != nil { return identity, err } return oidc.IdentityFromClaims(claims) }
// Verify claims in accordance with OIDC spec // http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation func VerifyClaims(jwt jose.JWT, issuer, clientID string) error { now := time.Now().UTC() claims, err := jwt.Claims() if err != nil { return err } ident, err := IdentityFromClaims(claims) if err != nil { return err } if ident.ExpiresAt.Before(now) { return errors.New("token is expired") } // iss REQUIRED. Issuer Identifier for the Issuer of the response. // The iss value is a case sensitive URL using the https scheme that contains scheme, // host, and optionally, port number and path components and no query or fragment components. if iss, exists := claims["iss"].(string); exists { if !urlEqual(iss, issuer) { return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss) } } else { return errors.New("missing claim: 'iss'") } // iat REQUIRED. Time at which the JWT was issued. // Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z // as measured in UTC until the date/time. if _, exists := claims["iat"].(float64); !exists { return errors.New("missing claim: 'iat'") } // aud REQUIRED. Audience(s) that this ID Token is intended for. // It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. // It MAY also contain identifiers for other audiences. In the general case, the aud // value is an array of case sensitive strings. In the common special case when there // is one audience, the aud value MAY be a single case sensitive string. if aud, ok, err := claims.StringClaim("aud"); err == nil && ok { if aud != clientID { return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID) } } else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok { if !containsString(clientID, aud) { return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID) } } else { return errors.New("invalid claim value: 'aud' is required, and should be either string or string array") } return nil }
func (o *testOIDCClient) verifyJWT(jwt jose.JWT) error { claims, err := jwt.Claims() if err != nil { return err } claim, _, _ := claims.StringClaim("test") if claim != "good" { return errors.New("bad token") } return nil }
func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) { jwtBytes := []byte(jwt.Data()) for _, k := range keys { v, err := k.Verifier() if err != nil { return false, err } if v.Verify(jwt.Signature, jwtBytes) == nil { return true, nil } } return false, nil }
func validateJWTSignature(jwt *jose.JWT, jwkSet *jwkSet) (bool, error) { for _, jwk := range jwkSet.Keys { v, err := jose.NewVerifier(jwk) if err != nil { return false, err } if err := v.Verify(jwt.Signature, []byte(jwt.Data())); err == nil { return true, nil } } return false, nil }
func (c *Client) VerifyJWTForClientID(jwt jose.JWT, clientID string) error { var keysFunc func() []key.PublicKey if kID, ok := jwt.KeyID(); ok { keysFunc = c.keysFuncWithID(kID) } else { keysFunc = c.keysFuncAll() } v := oidc.NewJWTVerifier( c.providerConfig.Get().Issuer.String(), clientID, c.maybeSyncKeys, keysFunc) return v.Verify(jwt) }
func (c *Client) VerifyJWT(jwt jose.JWT) error { var keysFunc func() []key.PublicKey if kID, ok := jwt.KeyID(); ok { keysFunc = c.keysFuncWithID(kID) } else { keysFunc = c.keysFuncAll() } v := NewJWTVerifier( c.providerConfig.Issuer, c.credentials.ID, c.maybeSyncKeys, keysFunc) return v.Verify(jwt) }
// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT. // Returns the client ID if valid, or an error if invalid. func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) { claims, err := jwt.Claims() if err != nil { return "", fmt.Errorf("failed to parse JWT claims: %v", err) } iss, ok, err := claims.StringClaim("iss") if err != nil { return "", fmt.Errorf("failed to parse 'iss' claim: %v", err) } else if !ok { return "", errors.New("missing required 'iss' claim") } else if !urlEqual(iss, issuer) { return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss) } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", fmt.Errorf("failed to parse 'sub' claim: %v", err) } else if !ok { return "", errors.New("missing required 'sub' claim") } if aud, ok, err := claims.StringClaim("aud"); err == nil && ok { if aud != sub { return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub) } } else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok { if !containsString(sub, aud) { return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub) } } else { return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array") } now := time.Now().UTC() exp, ok, err := claims.TimeClaim("exp") if err != nil { return "", fmt.Errorf("failed to parse 'exp' claim: %v", err) } else if !ok { return "", errors.New("missing required 'exp' claim") } else if exp.Before(now) { return "", fmt.Errorf("token already expired at: %v", exp) } return sub, nil }
// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT. // Returns the client ID if valid, or an error if invalid. func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) { claims, err := jwt.Claims() if err != nil { return "", fmt.Errorf("failed to parse JWT claims: %v", err) } iss, ok, err := claims.StringClaim("iss") if err != nil { return "", fmt.Errorf("failed to parse 'iss' claim: %v", err) } else if !ok { return "", errors.New("missing required 'iss' claim") } else if !urlEqual(iss, issuer) { return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss) } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", fmt.Errorf("failed to parse 'sub' claim: %v", err) } else if !ok { return "", errors.New("missing required 'sub' claim") } aud, ok, err := claims.StringClaim("aud") if err != nil { return "", fmt.Errorf("failed to parse 'aud' claim: %v", err) } else if !ok { return "", errors.New("missing required 'aud' claim") } if sub != aud { return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub) } now := time.Now().UTC() exp, ok, err := claims.TimeClaim("exp") if err != nil { return "", fmt.Errorf("failed to parse 'exp' claim: %v", err) } else if !ok { return "", errors.New("missing required 'exp' claim") } else if exp.Before(now) { return "", fmt.Errorf("token already expired at: %v", exp) } return sub, nil }
func (s *grpcServer) authToken(jwt jose.JWT, mustBeAdmin bool) (string, string, error) { claims, err := jwt.Claims() if err != nil { return "", "", fmt.Errorf("auth.go: failed to get claims %v", err) } aud, ok, err := claims.StringClaim("aud") if err != nil || !ok || aud == "" { return "", "", fmt.Errorf("auth.go: failed to parse 'aud' claim: %v", err) } err = s.server.Oidc.VerifyJWT(jwt, aud) if err != nil { return "", "", fmt.Errorf("auth.go: Failed to verify signature: %v", err) } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", "", fmt.Errorf("auth.go: failed to parse 'sub' claim: %v", err) } if !ok || sub == "" { return "", "", fmt.Errorf("auth.go: missing required 'sub' claim") } email, ok, err := claims.StringClaim("email") if err != nil || !ok || email == "" { return "", "", fmt.Errorf("auth.go: failed to parse 'email' claim: %v", err) } if mustBeAdmin { typ, ok, err := claims.StringClaim(OtsimoUserTypeClaim) if err != nil { return "", "", fmt.Errorf("auth.go: failed to parse '%s' claim: %v", OtsimoUserTypeClaim, err) } if !ok || typ == "" { return "", "", fmt.Errorf("auth.go: missing required '%s' claim", OtsimoUserTypeClaim) } if typ != OtsimoAdminUserType { return "", "", fmt.Errorf("auth.go: user must be admin") } } return sub, email, nil }
func (r *idTokenRefresher) Verify(jwt jose.JWT) error { claims, err := jwt.Claims() if err != nil { return err } now := time.Now() exp, ok, err := claims.TimeClaim("exp") switch { case err != nil: return fmt.Errorf("failed to parse 'exp' claim: %v", err) case !ok: return errors.New("missing required 'exp' claim") case exp.Before(now): return fmt.Errorf("token already expired at: %v", exp) } return nil }
func getUserIdAndEmail(jwt jose.JWT) (string, string, error) { claims, err := jwt.Claims() if err != nil { return "", "", fmt.Errorf("auth.go: failed to get claims %v", err) } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", "", fmt.Errorf("auth.go: failed to parse 'sub' claim: %v", err) } if !ok || sub == "" { return "", "", fmt.Errorf("auth.go: missing required 'sub' claim") } email, ok, err := claims.StringClaim("email") if err != nil || !ok || email == "" { return "", "", fmt.Errorf("auth.go: failed to parse 'email' claim: %v", err) } return sub, email, nil }
func GetJWTIssuer(jwt jose.JWT) (issuer string, audiences []string, err error) { claims, err := jwt.Claims() if err != nil { return "", audiences, fmt.Errorf("failed to parse JWT claims: %v", err) } iss, ok, err := claims.StringClaim("iss") if err != nil { return "", audiences, fmt.Errorf("Failed to parse 'iss' claim: %v", err) } else if !ok { return "", audiences, errors.New("Missing required 'iss' claim") } if aud, ok, err := claims.StringClaim("aud"); err == nil && ok { audiences = append(audiences, aud) } else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok { audiences = aud } else { return "", audiences, errors.New("Missing required 'aud' claim.") } return iss, audiences, nil }
func handleTokenFunc(srv OIDCServer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.Header().Set("Allow", "POST") phttp.WriteError(w, http.StatusMethodNotAllowed, fmt.Sprintf("POST only acceptable method")) return } err := r.ParseForm() if err != nil { log.Errorf("error parsing request: %v", err) writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), "") return } state := r.PostForm.Get("state") user, password, ok := r.BasicAuth() if !ok { log.Errorf("error parsing basic auth") writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidClient), state) return } creds := oidc.ClientCredentials{ID: user, Secret: password} var jwt *jose.JWT var refreshToken string grantType := r.PostForm.Get("grant_type") switch grantType { case oauth2.GrantTypeAuthCode: code := r.PostForm.Get("code") if code == "" { log.Errorf("missing code param") writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), state) return } jwt, refreshToken, err = srv.CodeToken(creds, code) if err != nil { log.Errorf("couldn't exchange code for token: %v", err) writeTokenError(w, err, state) return } case oauth2.GrantTypeClientCreds: jwt, err = srv.ClientCredsToken(creds) if err != nil { log.Errorf("couldn't creds for token: %v", err) writeTokenError(w, err, state) return } case oauth2.GrantTypeRefreshToken: token := r.PostForm.Get("refresh_token") if token == "" { writeTokenError(w, oauth2.NewError(oauth2.ErrorInvalidRequest), state) return } jwt, err = srv.RefreshToken(creds, token) if err != nil { writeTokenError(w, err, state) return } default: log.Errorf("unsupported grant: %v", grantType) writeTokenError(w, oauth2.NewError(oauth2.ErrorUnsupportedGrantType), state) return } t := oAuth2Token{ AccessToken: jwt.Encode(), IDToken: jwt.Encode(), TokenType: "bearer", RefreshToken: refreshToken, } b, err := json.Marshal(t) if err != nil { log.Errorf("Failed marshaling %#v to JSON: %v", t, err) writeTokenError(w, oauth2.NewError(oauth2.ErrorServerError), state) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(b) } }