Why am I getting an unsupported_grant_type error?

After following the SSO flow, my api gets a form-encoded post that looks like the following:

Parameters: {"state"=>"x", "code"=>"y", "id_token"=>"z"}


I then attempt to validate the code by calling `validate_auth_token`

def validate_auth_token(token, is_refresh = false)
      uri = URI.parse('https://appleid.apple.com/auth/token')
      https = Net::HTTP.new(uri.host, uri.port)
      https.use_ssl = true
      headers = { 'Content-Type': 'text/json' }
      request = Net::HTTP::Post.new(uri.path, headers)
      request_body = {
        client_id: @client_id,
        client_secret: retreive_client_secret
      }

      if is_refresh
        request_body[:grant_type] = 'refresh_token'
        request_body[:refresh_token] = token
      else
        request_body[:grant_type] = 'authorization_code'
        request_body[:code] = token
        request_body[:redirect_uri] = "https://#{Rails.application.secrets.backend_host_port}/apple"
      end

      pp request_body
      request.body = request_body.to_json
      response = https.request(request)
      p JSON.parse response.body
      p response.code
    end

    private

    def retreive_client_secret
      cert = retreive_secret_cert
      ecdsa_key = OpenSSL::PKey::EC.new cert
      algorithm = 'ES256'

      headers = {
        'alg': algorithm,
        'kid': @key_id
      }

      claims = {
        'iss': @team_id,
        'iat': Time.now.to_i,
        'exp': Time.now.to_i + 5.months.to_i,
        'aud': 'https://appleid.apple.com',
        'sub': @client_id
      }

      token = JWT.encode claims, ecdsa_key, algorithm, headers
      token
    end

where @client_id is the "Service ID" I submitted in the initial SSO request, @key_id is the id of the private key downloaded from the apple key dashboard, and @team_id is our apple team id.


No matter how I mishape the validation request, I continue to get a 400 response with an "unsupported_grant_type" error. The docs say that this means that: The authenticated client is not authorized to use the grant type.


I've enabled Apple SSO on the App Id, the Service Id, and have even verified my support email domains. What am I missing? Is there some other approval step I need to complete to get authorized?

Accepted Reply

The request to the token endpoint expects a POST with attrbutes as HTTP POST parameters as described in https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens


Your code above shows this:

request.body = request_body.to_json


Are you posting JSON data in which case the server will not recognize it and hence would compain about missing mandatory parameters such as grant_type.

Replies

The request to the token endpoint expects a POST with attrbutes as HTTP POST parameters as described in https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens


Your code above shows this:

request.body = request_body.to_json


Are you posting JSON data in which case the server will not recognize it and hence would compain about missing mandatory parameters such as grant_type.

Yes! That was exactly the problem, thank you! I also found that including 'alg' in the header of my client secret JWT was causing and 'invalid_client' error, so I removed it and things worked fine. Here's the working solution:


    def validate_auth_token(token, is_refresh = false)
      uri = URI.parse('https://appleid.apple.com/auth/token')
      https = Net::HTTP.new(uri.host, uri.port)
      https.use_ssl = true
      request_body = {
        client_id: @client_id,
        client_secret: retreive_client_secret
      }

      if is_refresh
        request_body[:grant_type] = 'refresh_token'
        request_body[:refresh_token] = token
      else
        request_body[:grant_type] = 'authorization_code'
        request_body[:code] = token
        request_body[:redirect_uri] = "https://#{Rails.application.secrets.backend_host_port}/apple"
      end

      request = Net::HTTP::Post.new(uri.path)
      request.set_form_data(request_body)

      response = https.request(request)
      JSON.parse response.body
    end

    def retreive_client_secret
      cert = retreive_secret_cert
      ecdsa_key = OpenSSL::PKey::EC.new cert
      algorithm = 'ES256'

      headers = {
        'kid': @key_id
      }

      claims = {
        'iss': @team_id,
        'iat': Time.now.to_i,
        'exp': Time.now.to_i + 5.minutes.to_i,
        'aud': 'https://appleid.apple.com',
        'sub': @client_id
      }

      token = JWT.encode claims, ecdsa_key, algorithm, headers
      token
    end