{"record":{"id":"8e5754c04ee8fddd","repo":"Freika/dawarich","slug":"blank-token","errorCode":null,"errorMessage":"blank token","messagePattern":"blank token","errorType":"exception","errorClass":"Auth::VerifyAppleToken::InvalidToken","httpStatus":401,"severity":"error","filePath":"app/services/auth/verify_apple_token.rb","lineNumber":14,"sourceCode":"# frozen_string_literal: true\n\nmodule Auth\n  class VerifyAppleToken\n    class InvalidToken < StandardError; end\n\n    def initialize(id_token, nonce: nil, client_id: nil)\n      @id_token = id_token\n      @nonce = nonce\n      @client_id = client_id\n    end\n\n    def call\n      raise InvalidToken, 'blank token' if @id_token.blank?\n      raise InvalidToken, 'client_id not configured' if effective_client_id.blank?\n\n      decoded = AppleID::IdToken.decode(@id_token)\n      verify_args = { client: effective_client_id }\n      verify_args[:nonce] = expected_nonce_hash if @nonce.present?\n\n      decoded.verify!(**verify_args)\n\n      log_missing_nonce_breadcrumb if @nonce.blank?\n\n      {\n        sub: decoded.sub,\n        email: decoded.email,\n        email_verified: decoded.email_verified?,\n        is_private_email: decoded.is_private_email?\n      }\n    rescue AppleID::IdToken::VerificationFailed, JSON::JWT::Exception => e\n      raise InvalidToken, e.message","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/services/auth/verify_apple_token.rb#L1-L32","documentation":"Auth::VerifyAppleToken raises InvalidToken 'blank token' when the id_token argument is nil or empty before any Apple library call happens. This is a fail-fast precondition: the Sign in with Apple flow always returns a credential containing an id_token (JWT), so a blank one means the client never completed the Apple handshake or the server-side code read the wrong parameter key.","triggerScenarios":"Apple OAuth callback where params[:id_token] (or the controller's chosen key) is absent - e.g. the mobile/web client sent only an authorization code, the credential was serialized under a different field name, or a user hit the endpoint directly without going through Apple first.","commonSituations":"Client confusion between Apple's authorization code flow and id_token flow (code needs a different exchange endpoint before any token exists), frontends sending credential/identityToken under a mismatched param name, POST body parsing dropping the token (form-encoded vs JSON mismatch), Apple returning an error the caller ignored before POSTing.","solutions":["Confirm the client actually obtained an Apple credential and is sending its identityToken/id_token in the request","Check the controller passes the correct param key to VerifyAppleToken.new(params[:id_token], ...)","If the client only has an authorization code, exchange it for tokens first - the code itself is not an id_token","Ensure the request body content-type matches what the controller parses"],"exampleFix":"# before\nAuth::VerifyAppleToken.new(params[:access_token]).call # wrong token type, often blank\n\n# after\nAuth::VerifyAppleToken.new(params[:id_token]).call # Apple's identity token (JWT)","handlingStrategy":"validation","validationCode":"def apple_id_token_present?(params)\n  params[:id_token].present?\nend\n# return a 400 with a clear message before entering the OAuth flow when false","typeGuard":null,"tryCatchPattern":"begin\n  result = Auth::VerifyAppleToken.new(id_token, nonce:, client_id:).call\nrescue Auth::VerifyAppleToken::InvalidToken => e\n  render json: { error: e.message }, status: :unauthorized\nend","preventionTips":["Assert the client completed Apple's credential flow and is sending identityToken under the agreed param name","Do not conflate Apple's authorization code with the id_token - only the latter works here","Distinguish precondition failures (blank token) from verification failures in logs; they have different fixes"],"tags":["authentication","apple-signin","oauth","jwt"],"backgroundTag":"missing-auth-token","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}