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 }
// 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) }
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 }
// 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 }
// 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 (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 (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 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 }