Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kylmakalle/devicecheck

Reduce fraudulent use of your services by managing device state and asserting app integrity via Apple DeviceCheck API with this Python wrapper.
https://github.com/kylmakalle/devicecheck

api apple appsec devicecheck ios macos python security swift

Last synced: 5 days ago
JSON representation

Reduce fraudulent use of your services by managing device state and asserting app integrity via Apple DeviceCheck API with this Python wrapper.

Awesome Lists containing this project

README

        

# Apple DeviceCheck

[Accessing and Modifying Per-Device Data](https://developer.apple.com/documentation/devicecheck/accessing_and_modifying_per-device_data)

Use a token from your app to validate requests, query and modify two per-device binary digits stored on Apple servers.

# Features

- Prevent API & Content abuse with validating requests via Apple device token
- Query and modify two bits of data to achieve up to **four remote states** saved on Apple servers
- Easy to use configuration
- [Examples](tests/integration)
- Integrations with modern web frameworks

# Prepare

Visit https://developer.apple.com/account/resources/authkeys/list and create new **Key** with **DeviceCheck** permission

# Install

```
pip install devicecheck
```

# Usage (Python)

### Setup

```python
from devicecheck import DeviceCheck

device_check = DeviceCheck(
team_id="XX7AN23E0Z", # https://developer.apple.com/account/#/membership/
bundle_id="com.akentev.app",
key_id="JSAD983ENA", # Generated at https://developer.apple.com/account/resources/authkeys/list
private_key="/path/to/AuthKey_JSAD983ENA.p8",
# Generated file at https://developer.apple.com/account/resources/authkeys/list
dev_environment=True, # True if using development Apple environment, False if using in production.
# Remember to set dev_environment=False in production!
)
```

### Asyncio setup
```python
from devicecheck.asyncio import AsyncioDeviceCheck
```
The rest will be the same, except for network methods must be `await`'ed

### Validate device

```python
result = device_check.validate_device_token(device_token)

if result.is_ok:
print('OK! Device is valid')
else:
print('Bad news. Unable to validate device')
```

### Update bits data

```python
# Can use both integers, strings and booleans. Will be converted with bool(value)
result = device_check.update_two_bits(device_token, bit_0=1, bit_1=False)

# Can update bits separately
result = device_check.update_two_bits(device_token, bit_0=True)

if result.is_ok:
print('Bits updated')
else:
print(f'Something went wrong. {result}')
```

### Query bits data

```python
# Can use both integers, strings and booleans
result = device_check.query_two_bits(device_token)

if result.is_ok:
print(f'First bit {result.bit_0}') # True
print(f'Second bit {result.bit_1}') # False
print(f'Last update time {result.bits_last_update_time}') # 2020-04
else:
print(f'Something went wrong. {result}')
```

# Web server decorators

You can easily integrate devicecheck to your webserver using a decorator. Specify a supported framework, or leave `None`
to try universal parser.

```python
from devicecheck.decorators import validate_device # for sync code
from devicecheck.decorators import DCSupportedFrameworks
from devicecheck import DeviceCheck

device_check = DeviceCheck(...)

# Set response that will be returned on invalid token
INVALID_TOKEN_RESPONSE = ('Invalid device_token', 403)

@app.route('/validate')
@validate_device(device_check, framework=DCSupportedFrameworks.flask, on_invalid_token=INVALID_TOKEN_RESPONSE)
def endpoint():
return 'Content'
```

## Sync code

Use sync decorator

```python
from devicecheck.decorators import validate_device
from devicecheck.decorators import DCSupportedFrameworks
```

### Flask

```python
INVALID_TOKEN_RESPONSE = ('Invalid device_token', 403)
framework = DCSupportedFrameworks.flask
```

## Async code

Use Async decorator

```python
from devicecheck.decorators import async_validate_device
from devicecheck.decorators import DCSupportedAsyncFrameworks
```

### Sanic

```python
from sanic.response import text

INVALID_TOKEN_RESPONSE = text('Invalid device_token', status=403)
framework = DCSupportedAsyncFrameworks.sanic
```

### FastAPI

```python
from fastapi.responses import PlainTextResponse

INVALID_TOKEN_RESPONSE = PlainTextResponse('Invalid device_token', status_code=403)
framework = DCSupportedAsyncFrameworks.fastapi
```

# Tests & Mock
Well, it's kinda hard to automate testing, because Devicecheck requires real device (Simulators won't work). In case you
need to disable decorators, pass `SKIP_DEVICE_CHECK_DECORATOR=True` environment variable.

You can also mock validation, pass `MOCK_DEVICE_CHECK_DECORATOR_TOKEN=XXXXXXXXXXXXX`, it will be a hardcoded valid token
value.

```bash
MOCK_DEVICE_CHECK_DECORATOR_TOKEN="device-check-token" python -m unittest tests/integrational/main.py
```

For Debug logs, including requests body, pass a `DEBUG` environment variable.

# Exceptions

Library represents an `AppleException` class with attributes `status_code` and `description`
Requires `raise_on_error=True` parameter for `DeviceCheck` instance.

# Usage (Swift)

### Generate device token

```swift
import DeviceCheck

public func getDeviceToken(completion: @escaping (String?) -> ()) {
if #available(iOS 11.0, *) {
let currentDevice = DCDevice.current
if currentDevice.isSupported
{
currentDevice.generateToken(completionHandler: { (data, error) in
if let tokenData = data {
let tokenString = tokenData.base64EncodedString()
print("Received device token")
completion(tokenString)
} else{
print("Error generating token: \(error!.localizedDescription)")
}
})
} else {
print("Device is not supported") // Simulators or etc.
}
} else {
print("Device OS is lower than iOS 11")
}
}

```

### Pass device token in HTTP request

Header or Body

```swift
getDeviceToken { deviceToken in
var request = URLRequest(url: "...")
request.httpMethod = "POST"

// Header
request.setValue(deviceToken, forHTTPHeaderField: "Device-Token")

// Body
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let json = ["device_token": deviceToken] as [String : Any]
let jsonData = try! JSONSerialization.data(withJSONObject: json)
request.httpBody = jsonData as Data

// Send it to server
let downloadTask = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
...
})
}
```

# License

[MIT](LICENSE)