Ben's Blog

I.T. ben April 02, 2020

PHP Zoom API JWT Bit Banging

It’s always hard to nail the exact sequence when authorizing through a new API. Here’s what I came up with for PHP authorization with Zoom’s JWTs. This is essentially a quick start which gets you enough functions to do a first API call: to list zoom users. Line 40->68 is where the JWt meat happens.

[php]
<?php

// config parameters you need to define
define( ‘ZOOM_TOKEN_FILE’, "/var/.zoom_token" ) ; // some location on the filesystem used to cache your token while it’s current (make sure permissions are restrictive)
define( ‘ZOOM_API_KEY’, "" ) ;
define( ‘ZOOM_API_SECRET’, "" ) ;

// main
print_r( zoom_list_users() ) ;
exit( 0 ) ;

// functions
function zoom_list_users() {
$users = array() ;

$page_number = 1 ;
$keep_going = true ;
while( $keep_going && $page_number<10000 ) {
$result = zoom_make_api_call( "GET", "https://api.zoom.us/v2/users", array(‘page_size’=>300, ‘page_number’=>$page_number, ‘status’=>"active") ) ;
$result = json_decode( $result, true ) ;
if( array_key_exists(‘users’, $result) &&
count($result[‘users’])>0 ) {
foreach( $result[‘users’] as $user ) {
$users[] = $user ;
}
$page_number++ ;
if( $page_number>$result[‘page_count’] ) {
$keep_going = false ;
}
} else {
$keep_going = false ;
}
}

return $users ;
}

// PHP’s default base64 encode isn’t URL safe which messes up the JWT, we need these functions instead
function base64_url_encode( $data ) {
return rtrim( strtr(base64_encode($data), ‘+/’, ‘-_’), ‘=’ ) ;
}
function base64_url_decode( $data ) {
return base64_decode( str_pad(strtr($data, ‘-_’, ‘+/’), strlen($data) % 4, ‘=’, STR_PAD_RIGHT) ) ;
}

function get_token( $refresh=false ) {
if( $refresh===false &&
file_exists(ZOOM_TOKEN_FILE) ) {
return file_get_contents( ZOOM_TOKEN_FILE ) ;
}

$jwt_request_date = @date( "U" ) ; // no warning, proper system timezone assumed
$jwt_expiration_date = $jwt_request_date + 60*60 ; # +1 hour
$jwt_header = ‘{"alg":"HS256","typ":"JWT"}’ ;
$jwt_claim_set = ‘{"iss":"’ . ZOOM_API_KEY . ‘","exp":’ . $jwt_expiration_date . ‘}’ ;
$jwt_signature = sign_data( base64_url_encode($jwt_header) . ‘.’ . base64_url_encode($jwt_claim_set), ZOOM_API_SECRET ) ;
$jwt = base64_url_encode( $jwt_header ) . "." . base64_url_encode( $jwt_claim_set ) . "." . base64_url_encode( $jwt_signature ) ;

file_put_contents( ZOOM_TOKEN_FILE, $jwt ) ;
return $jwt ;
}

function sign_data( $data, $key ) {
return hash_hmac( "SHA256" , $data, $key, true) ;
}

function zoom_make_api_call( $request, $url, $get_variables=null, $post_variables=null, $force_refresh_token=false ) {
$ch = curl_init() ;
$token = get_token( $force_refresh_token ) ;

$getfields = "" ;
if( $get_variables!==null && is_array($get_variables) ) {
foreach( $get_variables as $get_variable_key=>$get_variable_value ) {
$getfields .= "&" . urlencode( $get_variable_key ) . "=" . urlencode( $get_variable_value ) ;
}
if( strlen($getfields)>0 ) {
$getfields = "?" . substr( $getfields, 1 ) ;
}
}

curl_setopt( $ch, CURLOPT_URL, "{$url}{$getfields}" ) ;
curl_setopt( $ch, CURLOPT_PORT , 443 ) ;
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $request ) ;
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ) ;
if( $post_variables!==null && is_array($post_variables) ) {
$postfields = "" ;
foreach( $post_variables as $post_variable_key=>$post_variable_value ) {
$postfields .= "&" . urlencode( $post_variable_key ) . "=" . urlencode( $post_variable_value ) ;
}
$postfields = substr( $postfields, 1 ) ;
curl_setopt( $ch, CURLOPT_POSTFIELDS, $postfields ) ;
} else if( $post_variables!==null && is_string($post_variables) ) {
curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_variables ) ;
}
curl_setopt( $ch, CURLOPT_HTTPHEADER, array( "authorization: Bearer {$token}",
"content-type: application/json") ) ;

curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ) ;
curl_setopt( $ch, CURLOPT_HEADER, true ) ;

$response = curl_exec( $ch ) ;
curl_close( $ch ) ;

$response = parse_http_response( $response ) ;

if( $response[‘code’]=="200" ||
$response[‘code’]=="204" ||
$response[‘code’]=="404" ) {
return $response[‘body’] ;
} else if( $response[‘code’]=="401" ) { // expired token
if( $force_refresh_token===false ) {
// safe to recurse
return zoom_make_api_call( $request, $url, $get_variables, $post_variables, true ) ;
} else {
echo "ERROR: had an expired token and I tried to refresh it, yet somehow it’s still expired\n" ;
print_r( $response ) ;
exit( 1 ) ;
}
} else {
echo "ERROR: I have no idea what to do with this response from Zoom\n" ;
print_r( $response ) ;
exit( 1 ) ;
}
}

function parse_http_response( $raw_data ) {
$parsed_response = array( ‘code’=>-1, ‘headers’=>array(), ‘body’=>"" ) ;

$raw_data = explode( "\r\n", $raw_data ) ;

$parsed_response[‘code’] = explode( " ", $raw_data[0] ) ;
$parsed_response[‘code’] = $parsed_response[‘code’][1] ;
$i = 1 ;
if( $parsed_response[‘code’]=="100" ) {
$parsed_response[‘code’] = explode( " ", $raw_data[2] ) ;
$parsed_response[‘code’] = $parsed_response[‘code’][1] ;
$i = 3 ;
}

for( ; $i<count($raw_data) ; $i++ ) {
$raw_datum = $raw_data[$i] ;

$raw_datum = trim( $raw_datum ) ;
if( $raw_datum!="" ) {
if( substr_count($raw_datum, ‘:’)>=1 ) {
$raw_datum = explode( ‘:’, $raw_datum, 2 ) ;
$parsed_response[‘headers’][strtolower($raw_datum[0])] = trim( $raw_datum[1] ) ;
} else {
echo "ERROR: we’re in the headers section of parsing an HTTP section and no colon was found for line: {$raw_datum}\n" ;
exit( 1 ) ;
}
} else {
// we’ve moved to the body section
if( ($i+1)<count($raw_data) ) {
for( $j=($i+1) ; $j<count($raw_data) ; $j++ ) {
$parsed_response[‘body’] .= $raw_data[$j] . "\n" ;
}
}

// we don’t need to continue the $i loop
break ;
}
}

return $parsed_response ;
}

?>
[/php]

Recent Comments

  1. Don says:
    July 13, 2020 at 4:16 pm

    Thank you! I’ve been racking my head on getting the JWT portion sorted, and I didn’t want to use a library just to do it.

    Reply

Leave a Comment

Cancel reply

This blog is solar powered

Interactive

Handwriting Capture
Mandalagaba
IPv6 link-local to MAC converter
IPv6 MAC to link-local converter
Markov Text Generation
Markov Word Generation
Markov Music Generation
Duplogrifier
Flood Fill Algorithms
Homestead Metrics
RGB Playground
Web Games

Categories

  • aesthetics111
    • plots54
    • specular holography6
  • Books3
  • I.T.202
    • 3D modeling / printing21
    • AI6
    • all out geekery36
    • electronics27
    • homestead automation6
    • maniacal paranoia25
    • plotters49
    • unix / linux29
    • video games4
    • web development29
    • web games3
  • Lego / Duplo67
  • life in the U.S.42
  • miscellaneous202
  • nature encounters114
  • old vinyls3
  • organs2
  • self sustainability560
    • agriculture105
    • apiculture38
    • apple20
    • building131
    • canning3
    • crochet6
    • foraging6
    • hunting10
    • maple syrup47
    • poultry39
    • preserving2
    • solar power28
    • water23
    • wood84
  • trip to a new life6
Theme by Bloompixel. Proudly Powered by WordPress