Skip to content

Instantly share code, notes, and snippets.

@andyzinsser
Last active September 28, 2018 23:19
Show Gist options
  • Save andyzinsser/8044165 to your computer and use it in GitHub Desktop.
Save andyzinsser/8044165 to your computer and use it in GitHub Desktop.
Full flow of authenticating a Game Center Player on a third party server.
// request Game Center verification data for localPlayer from apple
- (void)authenticateGameCenterPlayer:(GKLocalPlayer *)localPlayer
{
[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
if (error) {
NSLog(@"ERROR: %@", error);
}
else {
// package data to be sent to server for verification
NSDictionary *params = @{@"publicKeyUrl": publicKeyUrl,
@"timestamp": [NSString stringWithFormat:@"%llu", timestamp],
@"signature": [signature base64EncodedStringWithOptions:0],
@"salt": [salt base64EncodedStringWithOptions:0],
@"playerID": localPlayer.playerID,
@"bundleID": [[NSBundle mainBundle] bundleIdentifier]};
// setup AFNetworking request to send the data to the server
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:APILinkWithGameCenterURL # update to your own service URL
parameters:params
constructingBodyWithBlock:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"%@", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"%@", error);
}];
}
}];
}
# NOTE: using django rest framework
class authenticateWithGameCenter(APIView):
def post(self, request):
response = {'errors': []}
# validation
publicKeyUrl = request.DATA.get('publicKeyUrl', None)
if publicKeyUrl is None:
response['errors'].append('publicKeyUrl is required.')
signature = request.DATA.get('signature', None)
if signature is None:
response['errors'].append('signature is required.')
salt = request.DATA.get('salt', None)
if salt is None:
response['errors'].append('salt is required.')
timestamp = request.DATA.get('timestamp', None)
if timestamp is None:
response['errors'].append('timestamp is required.')
playerID = request.DATA.get('playerID', None)
if playerID is None:
response['errors'].append('playerID is required.')
bundleID = request.DATA.get('bundleID', None)
if bundleID is None:
response['errors'].append('bundleID is required.')
if len(response['errors']) > 0:
return APIResponse(response)
decoded_sig = signature.decode('base64')
decoded_salt = salt.decode('base64')
# Download and read the .cer from apple and extract the public key
r = requests.get(publicKeyUrl, stream=True)
local_filename = publicKeyUrl.split('/')[-1]
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
f.flush()
der = open(local_filename, 'r').read()
x509 = crypto.load_certificate(crypto.FILETYPE_ASN1, der)
payload = playerID.encode('UTF-8') + bundleID.encode('UTF-8') + struct.pack('>Q', int(timestamp)) + decoded_salt
try:
verified = crypto.verify(x509, decoded_sig, payload, 'sha1')
logger.info('Successfully verified certificate with signaure')
except Exception as err:
logger.info(err)
response['errors'].append(err)
# TODO:
# Verify that a hash value of the payload matches the signature parameter provided by apple.
# If the generated and retrieved signatures match, the local player has been authenticated.
# Get or create a user for the given playerID
# authenticate a session for this player
# return the user
return APIResponse(response)
@johnou
Copy link

johnou commented Nov 18, 2016

@Maximusya isn't that the point of having a variable publicKeyUrl..? Current url is https://static.gc.apple.com/public-key/gc-prod-2.cer and if that needs to be revoked or renewed the new certificate would presumably be https://static.gc.apple.com/public-key/gc-prod-3.cer and so on..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment