Last active
September 7, 2025 10:51
-
-
Save alexgwalley/423c5abaef97ddec6fd026719e6869a2 to your computer and use it in GitHub Desktop.
Google OAuth 2.0 Get Access Token - Basic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define WINDOWS_LEAN_AND_MEAN | |
| #include <winsock2.h> | |
| #include <windows.h> | |
| #include <winhttp.h> | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| #pragma comment(lib, "Crypt32.lib") | |
| #pragma comment(lib, "bcrypt.lib") | |
| #pragma comment(lib, "ws2_32.lib") | |
| #pragma comment(lib, "winhttp.lib") | |
| void | |
| GenerateRandomBytes(void* buffer, size_t size) | |
| { | |
| BCryptGenRandom(NULL, buffer, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); | |
| } | |
| void | |
| Base64FromBinary(void* out_buffer, size_t* out_size, void* in_buffer, size_t in_buffer_size) | |
| { | |
| BOOL result = CryptBinaryToStringA(in_buffer, in_buffer_size, CRYPT_STRING_BASE64URI | CRYPT_STRING_NOCRLF, out_buffer, (DWORD*)out_size); | |
| // base64 buffer of '=' is not allowed for oauth stuff, so we have to trim it down here | |
| while ((*out_size) > 0 && ((char*)out_buffer)[(*out_size) - 1] == '=') { | |
| *out_size = (*out_size) - 1; | |
| ((char*)out_buffer)[(*out_size)] = '\0'; | |
| } | |
| } | |
| void | |
| SHA256(void* out_buffer, void* in_buffer, int32_t in_buffer_size) | |
| { | |
| BCRYPT_ALG_HANDLE hash_algorithm = 0; | |
| BCryptOpenAlgorithmProvider(&hash_algorithm, BCRYPT_SHA256_ALGORITHM, NULL, 0); | |
| DWORD hash_obj_length = 0; | |
| DWORD len = 0; | |
| BCryptGetProperty(hash_algorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hash_obj_length, sizeof(DWORD), &len, 0); | |
| PUCHAR hash_object = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, hash_obj_length); | |
| BCRYPT_HASH_HANDLE out_hash = NULL; | |
| BCryptCreateHash(hash_algorithm, &out_hash, hash_object, hash_obj_length, NULL, 0, 0); | |
| BCryptHashData(out_hash, in_buffer, in_buffer_size, 0); | |
| BCryptFinishHash(out_hash, out_buffer, 32, 0); | |
| if (out_hash) BCryptDestroyHash(out_hash); | |
| if (hash_object) HeapFree(GetProcessHeap(), 0, hash_object); | |
| if (hash_algorithm) BCryptCloseAlgorithmProvider(hash_algorithm, 0); | |
| } | |
| int main() | |
| { | |
| // IMPORTANT(agw): You need to put your own client_id and client_secret here. | |
| // make sure to include the .apps.googleusercontent.com on the client_id | |
| // storing the client_secret here really isn't safe and technically shouldn't be | |
| // required with PCKE, however google still requires it for some reason | |
| char* client_id = ""; | |
| char* client_secret = ""; | |
| unsigned char verifier[128]; | |
| unsigned char challenge[128]; | |
| size_t verifier_size = 128; | |
| size_t challenge_size = 128; | |
| // Generate pcke (Proof key for Code Exchange) | |
| // Here I am just generating random bytes then using base64 to make the bytes "verifier friendly" | |
| { | |
| // get random bytes | |
| unsigned char random_bytes[48]; // I just picked 48 here so the result length would be between the required 43-128 | |
| GenerateRandomBytes(random_bytes, sizeof(random_bytes)); | |
| // according to docs code_verifier must be A-Z, a-z, 0-9, -._~ -- 43-128 bytes long | |
| Base64FromBinary(verifier, &verifier_size, random_bytes, sizeof(random_bytes)); | |
| // Generate the challenge by SHA256'ing these bytes, technically this is optional but recommended | |
| unsigned char sha256[32]; // SHA256 always has 32 byte result | |
| SHA256(sha256, verifier, verifier_size); | |
| // Base64 encode to put in request | |
| Base64FromBinary(challenge, &challenge_size, sha256, sizeof(sha256)); | |
| } | |
| printf("Verifier: %.*s\n", (int)verifier_size, verifier); | |
| printf("Challenge: %.*s\n", (int)challenge_size, challenge); | |
| // - Ask user to open this URL in their browser. Applications could also use ShellExecute to do it automatically | |
| int port = 5781; | |
| printf("\nOpen this URL in a browser:\n"); | |
| printf("https://accounts.google.com/o/oauth2/v2/auth?" | |
| "client_id=%s" | |
| "&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A%d%%2F" // %d is hidden in here... | |
| "&response_type=code" | |
| "&scope=https%%3A%%2F%%2Fwww.googleapis.com%%2Fauth%%2Fphotoslibrary.readonly" // IMPORTANT: update scopes here | |
| "&code_challenge_method=S256" | |
| "&code_challenge=%s" | |
| "&access_type=offline\n", client_id, port, challenge); | |
| // Start HTTP server to listen to redirect, accept the authorization code from Google | |
| printf("\n\nStarting Server..."); | |
| char code_buf[512]; | |
| int code_buf_size = 0; | |
| { | |
| WSADATA data = {}; | |
| WSAStartup(MAKEWORD(2, 2), &data); | |
| SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | |
| struct sockaddr_in server = {}; | |
| server.sin_family = AF_INET; | |
| server.sin_port = htons(port); | |
| server.sin_addr.s_addr = INADDR_ANY; | |
| bind(listen_socket, (struct sockaddr*)&server, sizeof(server)); | |
| listen(listen_socket, SOMAXCONN); | |
| char buffer[2048] = {}; | |
| SOCKET client_socket = accept(listen_socket, NULL, NULL); | |
| int recv_size = recv(client_socket, buffer, sizeof(buffer)-1, 0); | |
| buffer[recv_size] = '\0'; | |
| printf("\n\n%s", buffer); | |
| printf("%s", buffer); | |
| char* start = strstr(buffer, "GET /?code="); | |
| if (start) { | |
| start += strlen("GET /?code="); | |
| char* end = strchr(start, '&'); | |
| if (end) { | |
| size_t len = end - start; | |
| if (len < sizeof(code_buf)) { | |
| strncpy(code_buf, start, len); | |
| code_buf[len] = '\0'; | |
| code_buf_size = len; | |
| } | |
| } | |
| } | |
| char* reply = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nYou may close this window."; | |
| send(client_socket, reply, (int)strlen(reply), 0); | |
| closesocket(listen_socket); | |
| closesocket(client_socket); | |
| WSACleanup(); | |
| } | |
| // ask to get token from google's oauth2 provider | |
| { | |
| // make post request, add body with code | |
| wchar_t* host = L"oauth2.googleapis.com"; | |
| wchar_t* path = L"/token"; | |
| char postdata[2048]; | |
| snprintf(postdata, sizeof(postdata), | |
| "code=%s" | |
| "&client_id=%s" | |
| "&client_secret=%s" | |
| "&code_verifier=%s" | |
| "&redirect_uri=http%%3A%%2F%%2Flocalhost%%3A5781%%2F" | |
| "&grant_type=authorization_code", | |
| code_buf, client_id, client_secret, verifier); | |
| HINTERNET session = WinHttpOpen(L"OAuth/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); | |
| HINTERNET connection = WinHttpConnect(session, host, INTERNET_DEFAULT_HTTPS_PORT, 0); | |
| HINTERNET request = WinHttpOpenRequest(connection, L"POST", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); | |
| wchar_t* headers = L"Content-Type: application/x-www-form-urlencoded"; | |
| BOOL success = WinHttpSendRequest(request, headers, -1L, (LPVOID)postdata, (DWORD)strlen(postdata), (DWORD)strlen(postdata), 0); | |
| WinHttpReceiveResponse(request, NULL); | |
| DWORD bytes_to_read = 0; | |
| DWORD bytes_read = 0; | |
| do | |
| { | |
| WinHttpQueryDataAvailable(request, &bytes_to_read); | |
| char* read_buffer = (char*)malloc(bytes_to_read + 1); | |
| ZeroMemory(read_buffer, bytes_to_read + 1); | |
| WinHttpReadData(request, read_buffer, bytes_to_read, &bytes_read); | |
| read_buffer[bytes_read] = '\0'; | |
| printf("Token Response: %s\n", read_buffer); | |
| /* | |
| NOTE(agw): example response | |
| { | |
| "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", | |
| "expires_in": 3920, | |
| "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly", // will be the scopes you provided | |
| "token_type": "Bearer" | |
| } | |
| */ | |
| free(read_buffer); | |
| } while (bytes_to_read != 0); | |
| WinHttpCloseHandle(request); | |
| WinHttpCloseHandle(connection); | |
| WinHttpCloseHandle(session); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment