julianschuh / dhl-paket-api

Inofficial DHL Paket API documentation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DHL Paket API

This repository contains findings on how the REST API of the DHL Paket App works as well as a handful of ruby scripts that demonstrate the usage of the API. The DHL Paket API enables a registered user to retrieve information about shipments from/to the user as well as the current mTan which is required to pick up shipments from a Packstation.

The focus lies on the parts of the API that are used to track shipments and retrieve the current mTan; the part of the API that handles purchasing of stamps and other services is out of scope.

API

Headers

Almost all request to the API require a specific set of headers to be present:

  • Client_id: OAuth client id, same for each request.
  • Interface-Key: hard-coded value as used in the Android app
  • Emmi-Api-Version: 7
  • Authorization: Bearer {ACCESS_TOKEN}

Valid values for Client_id and Interface-Key headers can be found in the common.rb file.

The headers must be included for all requests, unless stated otherwise.

Authentication

The authentication is performed via OAuth2 with RFC7636: The app opens the authentication URL https://mobil.dhl.de/oauth-web/oauth/grant in a embedded browser and waits for the user to authenticate. Upon successful authentication, the user is "redirected" to the non-existent URL which is intercepted by the browser. The URL is then transformed such that the parameters specified in the fragment of the URL can be extracted. The parameters contain the short-lived authorization code, which can be exchanged for the long-lived Access and Refresh Tokens.

The required steps to authenticate against the API are outlined in the following paragraphs

Step 1: Open the authentication URL in a browser

The authentication URL is constructed in the following way: https://mobil.dhl.de/oauth-web/oauth/grant?response_type=code&client_id={CLIENT_ID}&scope={SCOPE}&state={STATE}&code_challenge={CODE_CHALLENGE}&code_challenge_method={CODE_CHALLENGE_METHOD} CLIENT_ID, SCOPE and CODE_CHALLENGE_METHOD are fixed values that are hardcoded in the app. You can find appropriate values from the Android App in the file common.rb. STATE is a (in this case randomly generated base64) string that is passed to and returned back from the autorization server. In practice it's used to prevent CSRF attacks. CODE_CHALLENGE is the hash (generated with the method specified in CODE_CHALLENGE_METHOD, in this case SHA256) of a randomly generated byte sequence. It's encoded with a special base64-dialect, as specified in RFC7636 (urlsafe base64).

Step 2: Extract authorization code

After successful authentication, the user will be redirected to a non-existent URL similar to the following: https://app.dhl.de/android-dhl-parcel#state=....=&code=eyJra...long...code As you can see, the fragment of the URL contains the relevant data. The value of the state parameter should equal the value of the state parameter that was specified in the previous step. The code parameter contains the encrypted, base64-encoded authorization code which can be exchanged for the Access and Refresh Tokens in the next step.

Step 3: Exchange authorization code for Access and Refresh Tokens

The authorization code obtained in the previous step cannot be used for authentication directly. It must be exchanged for Access and Refresh Tokens.

To do this, a POST request must be sent to the URL https://app.dhl.de/oauth/grant/exchange. The body of the request consists of a JSON object that only contains a single attribute: code_verifier which contains the byte sequence that was used to generate the CODE_CHALLENGE hash in Step 1. It's encoded with the same base64-dialect as the hash. The request must include header described in the "Headers" section, with one exception: The Authorization header must have the following format:

  • Authorization: Grant {CODE}

Additionally, the code verifier already included in the body should be conveyed in the header, too:

  • Code_verifier: {verifier} (same as the code_verifier value in the body)

If successful, the response will contain a JSON-object with three fields: accessToken, refreshToken and accessValidity. accessValidity contains the duration during which the accessToken is valid. It's specified in Milliseconds. Default: 3600000, 1h.

Congratulations, you successfully retrieved the tokens required to authenticate against the API.

Step 4: Refresh Access Token

As indicated by the accessValidity field from Step 3, the obtained access token has a limited validity. After the specified duration, the Access Token cannot be used and API calls will result in a 401 response. In this case, the Refresh Token can be used to retrieve a new Access and Refresh Token: A POST request must be sent to the URL https://app.dhl.de/oauth/token/request. The body of the request may be empty, the relevant information is conveyed using the headers: Again, the headers described in the section "Headers" must be included, with one excpetion: The Authorization header must have the following format:

  • Authorization: Refresh {REFRESH_TOKEN}

The response will have the same format as the response in Step 3. It will contain a new Access Token and a new Refresh Token.

Retrieving Shipments

To retrieve a list of current shipments, send a POST request to https://app.dhl.de/shipments. The body should consist of the following JSON structure to retrieve a list of what the app considers to be current shipments.

{
	"shipmentsInCache": {
		"archivedShipmentsInCache": [],
		"completedShipmentsInCache": []
	},
	"includeCurrent": true,
	"includeArchived": false,
	"languageCode": "de"
}

To retrieve a list of older, archived shipments, set includeCurrent to false and includeArchived to true. Setting both values to true resulted in a server error during testing.

Premium Area

The app contains a view, internally referred to as the premium area, that displays at which locations (Packstations or Shops) shipments are ready for pickup. You can retrieve the information by sending a GET request to https://app.dhl.de/premium-area.

Customer Information

To retrieve information about the user, send a GET request to https://app.dhl.de/customer-information. On success, the server will return a 200 response with a JSON object. The object will include, besides others, the email address (email) and the Postnummer (postNumber).

mTan

The app also displays the current mTan, which is needed to pick up shipments from the Packstation. Due to fraudulent occurrences in the past (see heise.de), retrieving the mTan requires an additional step in order to verify that the person requesting the mTan has access to the phone, which is used as a second factor.

Step 1: Request mTan verification

The first step is to request verification. A SMS containing a verification code will be sent to the number associated with the account. To request the SMS, send a POST request to https://app.dhl.de/tan/request (empty body). Include the headers as described in the "Headers" section. Additionally, the X-Uhash header is required. It's a simple "hash" that is calculated from the Postnummer of the account (the Postnummer must be specified on all shipments to a packstation and is used to associate shipments with the correct user account). It can be calculated with the following bit of ruby code:

post_number = "07070707"
post_number.unpack("c*").inject(0) { |h, c| ((h * 31) + c) % 10000 }.to_s.rjust(4, "0")

The Postnummer must be specified as an ASCII string. It can optionally be retrieved from the customer-information API endpoint described in section "Customer Information".

On success, the server responds with 204.

Step 2: Perform verification

Once the SMS is received, a GET request must be sent to https://app.dhl.de/tan/token. The following header fields are required in addition to the fields described in the section "Headers":

  • X-Uhash: refer to the previous step
  • X-Targethint: 4
  • Tan: Confirmation code recieved via SMS

On success, the server will return a 200 response that contains a JSON object with one attribute: token. It contains a token that is used as an additional authentication factor only when retrieving the current mTan -- the mTan ultimately allows the pickup of shipments from a Packstation.

Step 3: Retrieve mTan

Now we have everything in place to actually retrieve the current mTan:

Just send a POST request to https://app.dhl.de/mTan.

The body of thre request should have the following format:

{
  "generate": false,
  "mtan": true
}

Also include the headers previously mentioned as well as an additional header for the token obtained in Step 2:

  • Token: {TOKEN}

On success, the server will return a 200 response containing a JSON object with the field mTan, which contains the current mTan. If no mTan is available, the server will return a 500 response that includes a JSON object with the field errorText which indicates that no mTan is available (or maybe something else).

Running the examples

The directory examples contains a bunch of simple ruby scripts demonstrating the usage of the API. They are interactive and will ask for required information. If you have problems pasting in the required values, run the script with the environment variable STTY set to -icanon, e.g. STTY=-icanon ruby auth.rb

  • common.rb: Defines commonly needed constants
  • auth.rb: Performs the authentication procedure and will print out Access and Refresh tokens on success
  • renew.rb: Renews the tokens with a Refresh token
  • shipments.rb: Retrieves and prints out (as JSON) the list of current shipments
  • premium_area.rb: Retrieves and prints out the shipments currently ready for pickup
  • tan_enable.rb: Will perform the verification procedure that results in the issuance of an additional token used for retrieving the current mTan
  • tan.rb: Retrieves the current mTan

About

Inofficial DHL Paket API documentation