`

Sign in with Apple REST API (Rails)

阅读更多

# 文档(Apple授权登录)

https://developer.apple.com/documentation/signinwithapplerestapi

# 获取公钥Key接口

https://appleid.apple.com/auth/keys

# 验证客户端发送的authorizationCode接口

https://appleid.apple.com/auth/token

 

 

require 'net/https'
require 'jwt'

class AppleAuth
    APPLE_ISSUER = 'https://appleid.apple.com'.freeze
    APPLE_CONFIG = {
      client_id:   ENV['CLIENT_ID'], # com.mytest.app
      key_id:      ENV['KEY_ID'], # 10位字符
      team_id:     ENV['TEAM_ID'], # 10位字符
      # Apple官方配置好下载下来文件内容
      private_key: ENV['PRIVATE_KEY'] # -----BEGIN PRIVATE KEY----- *** 
    }

    class << self
      def public_keys # 获取公钥keys
        uri = URI(File.join(APPLE_ISSUER, '/auth/keys'))
        res = Net::HTTP.get_response(uri)
        if res.code.to_i == 200
          JSON.parse(res.body)['keys']
        end
      end

      def jwt_token_info(jwt_token, key, verify, opts = {})
        payload, header = JWT.decode(jwt_token, key, verify, opts)
        {payload: payload, header: header}.stringify_keys
      end
    end

    # token:identityToken
    # auth_code:authorizationCode
    # opts.email 客户端email
    # opts.apple_uid: userId 信息
    # opts.idfa_str: IDFAStr 信息
    # opts.family_name
    # opts.given_name
    def initialize(token, auth_code, opts = {})
      @client_token = token
      @auth_code    = auth_code
      @opts         = opts.stringify_keys
    end

    # JWT 验证方式
    def jwt_verify?(opts = {})
      valid_token_info(opts).present?
    end
    
    def client_token_info # 得到Token内容时没有验证
      @_client_token_info = self.class.jwt_token_info(@client_token, nil, false)
    end

    def valid_token_info(opts = {}) # 验证通过得到Token内容
      opts.merge!(algorithm: client_token_info.dig('header', 'alg'))
      self.class.jwt_token_info(@client_token, public_key.keypair, true, opts)
    rescue
      {}
    end

    def auth_token_info # 通过/auth/token获取的id_token解析的信息
      res = cache_apple_code_auth
      self.class.jwt_token_info(res['id_token'], nil, false)
    end

    private

    def public_key(force = false) # 获取解析Token的对应公钥
      if @_public_key && !force
        @_public_key
      else
        if (_keys = self.class.public_keys)
          key          = _keys.find { |obj| obj['kid'] == client_token_info.dig('header', 'kid') }
          @_public_key = JWT::JWK.import(key.symbolize_keys) if key
        end
      end
    end

    def code_verify_params # 构建请求/auth/token的参数
      {
        grant_type:    'authorization_code',
        client_id:     APPLE_CONFIG[:client_id],
        client_secret: client_secret,
        code:          @auth_code,
      }
    end

    def client_secret
      payload   = {
        iss: APPLE_CONFIG[:team_id],
        iat: Time.current.to_i,
        exp: 10.minutes.after.to_i,
        aud: APPLE_ISSUER,
        sub: APPLE_CONFIG[:client_id]
      }
      header    = {
        # alg: 'ES256', 加上请求失败 "error"=>"invalid_request"
        kid: APPLE_CONFIG[:key_id]
      }
      ecdsa_key = OpenSSL::PKey::EC.new(APPLE_CONFIG[:private_key])

      JWT.encode(payload, ecdsa_key, 'ES256', header)
    end

    def cache_apple_code_auth(force = false)
      uri = URI(File.join(APPLE_ISSUER, '/auth/token'))
      res = Net::HTTP.post_form(uri, code_verify_params)
        if res.code == 200
          JSON.parse(res.body)
        else
          {}
        end
    end
  end

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics