Freika/dawarich · error · Auth::VerifyGoogleToken::InvalidToken

blank token

Error message

blank token

What it means

Raised by Auth::VerifyGoogleToken#call when the id_token passed to the service is nil or empty (ActiveSupport #blank? matches '', nil, and whitespace-only strings). The service deliberately fails fast before any network call to Google, because an empty token can never validate. It surfaces as Auth::VerifyGoogleToken::InvalidToken, a StandardError subclass the auth controller layer is expected to rescue.

Source

Thrown at app/services/auth/verify_google_token.rb:13

# frozen_string_literal: true

module Auth
  class VerifyGoogleToken
    class InvalidToken < StandardError; end

    def initialize(id_token, nonce: nil)
      @id_token = id_token
      @nonce = nonce
    end

    def call
      raise InvalidToken, 'blank token' if @id_token.blank?

      client_ids = [
        ENV['GOOGLE_IOS_CLIENT_ID'],
        ENV['GOOGLE_ANDROID_CLIENT_ID'],
        ENV['GOOGLE_OAUTH_CLIENT_ID']
      ].compact
      raise InvalidToken, 'Google client IDs not configured' if client_ids.empty?

      validator = GoogleIDToken::Validator.new
      claims = nil
      audience_error = nil

      client_ids.each do |client_id|
        claims = validator.check(@id_token, client_id)
        break if claims
      rescue GoogleIDToken::AudienceMismatchError => e
        audience_error = e
        next

View on GitHub (pinned to 97fad417c5)

Solutions

  1. Log/inspect the incoming request params at the controller to confirm the token field is present and non-empty before invoking the service.
  2. Fix the client so it only submits after the Google SDK resolves with a real id_token (e.g. handle google.accounts.id cancel callbacks instead of posting on every click).
  3. If the parameter name differs, align it (params.require(:id_token) or the credential field) between client and server.
  4. In tests, pass a real-shaped (even if unsigned) JWT string so the service gets past the blank check.

Example fix

// before
result = Auth::VerifyGoogleToken.new(params[:id_token]).call

// after
id_token = params[:id_token].to_s.strip
if id_token.empty?
  return render json: { error: 'id_token is required' }, status: :bad_request
end
result = Auth::VerifyGoogleToken.new(id_token).call
Defensive patterns

Strategy: validation

Validate before calling

id_token = params[:id_token].to_s.strip
raise ActionController::ParameterMissing, 'id_token' if id_token.empty?

Type guard

def valid_id_token?(token) = token.is_a?(String) && !token.strip.empty?

Try / catch

begin
  claims = Auth::VerifyGoogleToken.new(id_token).call
rescue Auth::VerifyGoogleToken::InvalidToken => e
  render json: { error: e.message }, status: :unauthorized
end

Prevention

When it happens

Trigger: Calling Auth::VerifyGoogleToken.new(id_token).call with params[:id_token] missing from the request, a mobile client that sends an empty credential after a cancelled Google sign-in flow, or a form/JS client that posts before the Google SDK returns a token. Any whitespace-only string also triggers it.

Common situations: Frontend sends the field name the backend does not expect (credential vs id_token), Google One Tap / Sign-In SDK returns null on user dismissal and the caller forwards it anyway, integration tests that stub the UI but not the token payload.

Related errors


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/09e14c4110ec2088. Report an issue: GitHub.