Sign in with Apple, Part 4: Web and Other Platforms

栏目: IT技术 · 发布时间: 4年前

内容简介:The fourth part of a series Sign in with Apple (And maybe the last part). This part is less related to us, iOS developer, let's explore it to see what we might need to do to support this on web and other platforms.As I remembered, Apple didn't mention OAut

The fourth part of a series Sign in with Apple (And maybe the last part). This part is less related to us, iOS developer, let's explore it to see what we might need to do to support this on web and other platforms.

  1. Sign in with Apple, Part 1: Apps
  2. Sign in with Apple, Part 2: Private Email Relay Service
  3. Sign in with Apple, Part 3: Backend – Token verification
  4. Sign in with Apple, Part 4: Web and Other Platforms

As I remembered, Apple didn't mention OAuth or OpenID Connect in their WWDC session or documentation. Luckily Apple didn't introduce their own wheel but adopt the existing open standards OAuth 2.0 and OpenID Connect ( Hybrid Flow ). They use the same terminology and API calls. If you're familiar with these technologies, Sign in with Apple shouldn't be a problem for you. If you aren't familiar with OAuth and OpenID Connect, don't worry. I will guide you through all of the flow and dance needed.

Let's begin from the frontend, the button.

Configuring Your Webpage for Sign In with Apple

Ensure your webpage is ready to authorize users through Sign In with Apple.

Embed Sign In with Apple JS in Your Webpage

Use the script tag and link to Apple’s hosted version of the Sign In with Apple JS framework:

<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
<html>
    <head>
        <meta name="appleid-signin-client-id" content="[CLIENT_ID]">
        <meta name="appleid-signin-scope" content="[SCOPES]">
        <meta name="appleid-signin-redirect-uri" content="[REDIRECT_URI]">
        <meta name="appleid-signin-state" content="[STATE]">
    </head>
    <body>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
    </body>
</html>

You can also configure the authorization object using the JavaScript APIs and display a Sign In with Apple button, as in the following example:

<html>
    <head>
    </head>
    <body>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript">
            AppleID.auth.init({
                clientId : '[CLIENT_ID]',
                scope : '[SCOPES]',
                redirectURI: '[REDIRECT_URI]',
                state : '[STATE]'
            });
        </script>
    </body>
</html>

Your page would look something like this:

Sign in with Apple, Part 4: Web and Other Platforms

At this moment, there is a bug in the script. You can monkey patch it with some CSS trick, here is mine:

.signin-button > div > div > svg {  
  height: 50px;  
  width: 100%;  
} 

Now we have a proper sign-in button.

Sign in with Apple, Part 4: Web and Other Platforms

If you click on the button right now, you will get an invalid client error, which is not a surprise since our value in meta tag still be placeholder value.

Sign in with Apple, Part 4: Web and Other Platforms

To make this work, we need to provide four values. I will show you how to get those values (client id, scopes, redirect URL, and state).

Client ID & Redirect URL

First, you need to have a client id to represent your application. Apple called this Services ID in the Apple developer portal.

  1. Go to Identifiers menu in Certificates, Identifiers & Profiles
  2. Choose Services IDs

Sign in with Apple, Part 4: Web and Other Platforms

client_id

Sign in with Apple, Part 4: Web and Other Platforms

  1. Click the Configure button next to Sign in with Apple . This is where you define the domain your app is running and redirect URLs for OAuth flow.

Sign in with Apple, Part 4: Web and Other Platforms

We just created a Services ID ( client_id ) and a redirect URL that are required to initiate Sign in with Apple (Authentication Requeststep in OpenID Connect flow).

The scope is the amount of user information requested from Apple. Right now, there are only two options name and email . You can request the user’s name or email. You can also choose to request both or neither.

State is a parameter defined in OAuth protocolused to mitigate CSRF attacks. It can be any string; just make sure it is unique and non-guessable value.

After you know how to get and generate those parameters, replace all the placeholders, and try signing in again. This time everything would work as it should be. You will be redirected to Apple.com and prompt with Sign in form.

Sign in with Apple, Part 4: Web and Other Platforms

Configuring Sign In with Apple Buttons

Use CSS styles and data attributes to display and configure Sign In with Apple buttons in browsers.

Control the size of the button by adding a class that contains the desired CSS width and height styles.

.signin-button {
  width: 210px;
  height: 40px;
}

The above example is coming from the Apple documentation, but it does not work as expected at the moment. I hack around with the following instead.

.signin-button > div > div > svg {  
  height: 50px;  
  width: 100%;  
} 

You can change the text in the button by setting the data-type property to one of the following values:

sign in
continue

Specify the background color of the Sign In with Apple button by setting the data-color property to one of the following values:

black
white

Specify the border for the Sign In with Apple button by setting the data-border property to one of the following values:

false
true

Use CSS to control the corner radius like you normally do.

.signin-button {
  border-radius: 10px;
}

We are not finished yet, but let see how far we can go with the information we have now.

  1. Click Sign in with Apple button now direct you to Apple.com and prompt users to sign in.

Sign in with Apple, Part 4: Web and Other Platforms

If this is your first time, you might see two-factor authentication dialog.

Sign in with Apple, Part 4: Web and Other Platforms

  1. After pass two-factor authentication, you will see a prompt confirming that you want to sign in to this application using your Apple ID along with information that will be shared with the app.

Sign in with Apple, Part 4: Web and Other Platforms

  1. You can edit that information by click on Edit buttons

Sign in with Apple, Part 4: Web and Other Platforms

  1. Click Continue and you will be redirected back to your app, which will be failed since we didn't implement any logic to handle the redirect.

Sign in with Apple, Part 4: Web and Other Platforms

As you can see, we can go a bit far with the information we got. We redirected back to what we put in <meta name="appleid-signin-redirect-uri" content="[REDIRECT_URI]"> ( https://siwa-example.herokuapp.com/redirect ).

Handle the Redirect

This is where we handle Authentication Response . The default response_type for Authentication Request generated from Apple JS is code id_token , so we would get state , id_token , and code along with user in the response.

The Apple default button is making a request with response_mode equal to form_post (which is required if you requested any scopes) which will make an HTTP POST to the client with Authorization Response parameters.

Here is a very simple Rails application to handle the POST response.

Payload:

{
   "state": "xxx",
   "code": "yyy",
   "id_token": "zzz",
   "user": {
        "name": {
            "firstName":"John",
            "lastName":"Doe"
        },
        "email":"example@privaterelay.appleid.com"
    }
}

Application code:

# home_controller.rb
class HomeController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :redirect
  
  def index
    state = Digest::SHA1.hexdigest(SecureRandom.random_bytes(1024))
    session[:state] = state
  end

  def redirect
    @state = params[:state]
    @code = params[:code]
    @id_token = params[:id_token]
    @user = parmas[:user]

    # TODO: Validation

    render 'redirect'
  end
end

# redirect.html.erb
Redirect Completed

<h1>Code</h1>
<p><%= @code %></p>

<h1>State(<%= session[:state] %>)</h1>
<p><%= @state %></p>

<h1>ID TOKEN</h1>
<p><%= @id_token %></p>

<h1>User</h1>
<p><%= @user %></p>

Sign in again, but this time the redirect will be successful with four values code , state , user , and id_token .

Sign in with Apple, Part 4: Web and Other Platforms

Response Validation

At this point, we got everything we need to create an account. You can get sub (Apple user's unique ID) and user information in user , but don't just blindly trust everything from a network. You need to validate these information before use.

We use state to mitigate Cross-Site Request Forgery. We verify the state parameter by matches the one we sent at the beginning with the one we get back from the response.

def redirect
  @code = params[:code]

  if @code.present? && session[:state] == @state
    session.delete(:state)
    ...
  end
  ...
end

Full detail spec can be found here

ID Token (id_token) Validation

ID Tokenis a JSON Web Token (JWT) contain a set of user attributes, which are called claims .

Apple id_token contains following information:

// Header
{
  "kid": "AIDOPK1",
  "alg": "RS256"
}
// Payload
{
  "iss": "https://appleid.apple.com",
  "aud": [Services ID that we registered at the beginning],
  "exp": 1579073561,
  "iat": 1579072961,
  "sub": [Apple User Identifier],
  "c_hash": "Q4ZkNP4SB2f-m9vtLfO0UA",
  "email": [EMAIL],
  "email_verified": "true",
  "is_private_email": "true",
  "auth_time": 1579072961
}

I already show you how to validate JWT in my previous article, Sign in with Apple, Part 3: Backend – Token verification . Following are the fields you should validate:

Key Description Note
iss (issuer) The issuer registered claim key. Should come from Apple ( https://appleid.apple.com in this case)
aud (audience) The audience registered claim key. Must matched a Services ID ( client_id ) that you created inClient ID & Redirect URL`
exp (expiration) The expiration time registered claim key. The current time MUST be before the time represented by the exp Claim
iat (issued at) The issued at registered claim key, the value of which indicates the time at which the token was generated. You can check elapsed time since this issued time if you need custom expiration duration.

You must also validate the signature of the ID Token with Apple's public key. I already wrote a rough how-to in my previous post How to verify the token . You can check it out there.

Full detail spec can be found here

Token Request flow exchange Authorization Code ( code ) with access_token and id_token . As we always do, we need to validate the Authorization code before doing the exchange.

To do that, we need to compare the code we get with c_hash (Code Hash) value in the id_token . You can't just compare it, as a name imply Code Hash is a hash value of code. To compare it, you need to know what Code Hash represented.

c_hash Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string.

If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type values code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL.

https://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken

To sum it up, these are steps you need to do:

c_hash

Full detail spec can be found here

Here is an example in Ruby:

c_hash = "xxx"
code = "yyy"
hash_code = Digest::SHA256.digest code
base64url_encode = Base64.urlsafe_encode64(hash_code[0, hash_code.size/2.to_i], padding: false)
    
if base64url_encode == c_hash
  # Valid
end

Exchange authorization code with an access token

The final part of the flow is to exchange authorization code for an access token. The endpoint and parameters are documents in Apple Documentation

Endpoint:

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

Parameters:

Key Description Note
client_id The application identifier for your com.sarunw.siwa.client in my case.
client_secret A secret generated as a JSON Web Token that uses the secret key generated by the WWDR portal. I will tell you how to get it in the
code The authorization code received from your application’s user agent. The code is single use only and valid for five minutes. The one that we got from Authorization Response
grant_type The grant type that determines how the client interacts with the server. For authorization code validation, use authorization_code . For refresh token validation requests, use refresh_token . Use authorization_code
redirect_uri The destination URI the code was originally sent to.

At this point, we can fill in every parameter but one, client_secret . Let's see how to get that.

Create a Sign in with Apple private key

To get a client_secret , we need to create an Apple private key for Sign in with Apple service first.

  1. Go to Identifiers menu in Certificates, Identifiers & Profiles

  2. Choose Key

  3. Click the blue plus icon to register a new key. Give your key a name, and check the Sign In with Apple checkbox.

    Sign in with Apple, Part 4: Web and Other Platforms
  4. Click the Configure button and select the primary App ID you created earlier.

    Sign in with Apple, Part 4: Web and Other Platforms
  5. Apple will generate a new private key for you and let you download it only once. Make sure you save this file because you won’t be able to get it back again later! The file you download will end in .p8. You also get key identifier ( kid ) (The key identifier appears below the key name.)

    Sign in with Apple, Part 4: Web and Other Platforms

Creating the Client Secret

Client secret is in JWT format with following header and payload:

// Header
{
  "kid": "[KEY_ID]",
  "alg": "ES256"
}
// Payload
{
  "iss": "[TEAM_ID]",
  "iat": 1579087819,
  "exp": 1594639819,
  "aud": "https://appleid.apple.com",
  "sub": "[CLIENT_ID]"
}

Header

Key Description Note
alg The algorithm used to sign the token. ES256 in this case.
kid A 10-character key identifier obtained from your developer account. We already got this Step 5. in the.

Payload

Key Description Note
iss The issuer registered claim key, which has the value of your 10-character Team ID, obtained from your developer account. Log in to your Apple Developer Account and click on Membership section on the left panel, you will see your Team ID there.
iat The issued at registered claim key, the value of which indicates the time at which the token was generated, in terms of the number of seconds since Epoch, in UTC.
exp The expiration time registered claim key, the value of which must not be greater than 15777000 (6 months in seconds) from the Current Unix Time on the server.
aud The audience registered claim key, the value of which identifies the recipient the JWT is intended for. Since this token is meant for Apple, use https://appleid.apple.com . Use https://appleid.apple.com
sub The subject registered claim key, the value of which identifies the principal that is the subject of the JWT. Use the same value as client_id as this token is meant for your application Use a Services ID that we created inClient ID & Redirect URL

Here comes another tricky part. After you have everything in place, you need to sign in with the private key generated fromCreate a Sign in with Apple private key.

From Apple Documentation

After creating the token, sign it using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm. Specify the value ES256 in the algorithm header key. Specify the key identifier in the kid attribute.

In my case, I use ruby-jwt

pem_content = <<~EOF
-----BEGIN PRIVATE KEY-----
xxxxx......
-----END PRIVATE KEY-----
EOF

ecdsa_key = OpenSSL::PKey::EC.new pem_content

headers = {
    'kid' => 'key_id'
}

claims = {
    'iss' => 'team_id',
    'iat' => Time.now.to_i,
    'exp' => Time.now.to_i + 86400*180,
    'aud' => 'https://appleid.apple.com',
    'sub' => 'client_id',
}

token = JWT.encode claims, ecdsa_key, 'ES256', headers

This is the JWT client secret.

token = JWT.encode claims, ecdsa_key, 'ES256', headers

This article should give you enough information for you to implement Sign in with Apple yourself (if you are a solo developer) or if you have a backed team, you should be able to find every value your colleagues would ask for.

I can't cover all the detail here (I provided a lot of references insection and footnote for further reading), but I think I cover all the basics you should know to implement Sign in with Apple in your application.

If you have any feedback, comment, or any mistakes on this series, you can contact me via email or Twitter; I would love to hear from you.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

原则

原则

[美] 瑞·达利欧 / 刘波、綦相 / 中信出版社 / 2018-1 / CNY 98.00

※ 华尔街投资大神、对冲基金公司桥水创始人,人生经验之作 作者瑞·达利欧出身美国普通中产家庭,26岁时被炒鱿鱼后在自己的两居室内创办了桥水,现在桥水管理资金超过1 500亿美元,截至2015年年底,盈利超过450亿美元。达利欧曾成功预测2008年金融危机,现在将其白手起 家以来40多年的生活和工作原则公开。 ※ 多角度、立体阐述生活、工作、管理原则 包含21条高原则、139条中原......一起来看看 《原则》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具