Sample code for 30+ languages & platforms
Unicode C

Azure OpenID Connect Step 2 -- Get id_token and Validate

See more OIDC Examples

After getting the endpoints by querying the Azure's OIDC well-known discovery document (OpenID Configuration document), we use the authorization_endpoint to get the id_token, and then validate it..

Chilkat Unicode C Downloads

Unicode C
#include <C_CkHttpRequestW.h>
#include <C_CkPrngW.h>
#include <C_CkStringBuilderW.h>
#include <C_CkSocketW.h>
#include <C_CkTaskW.h>
#include <C_CkOAuth2W.h>
#include <C_CkHashtableW.h>
#include <C_CkJwtW.h>
#include <C_CkJsonObjectW.h>
#include <C_CkHttpW.h>
#include <C_CkPublicKeyW.h>

void ChilkatSample(void)
    {
    BOOL success;
    const wchar_t *authorization_endpoint;
    const wchar_t *jwks_uri;
    HCkHttpRequestW req;
    HCkPrngW prng;
    const wchar_t *encodedParams;
    HCkStringBuilderW sbUrl;
    const wchar_t *url;
    HCkSocketW listenSock;
    HCkSocketW browserSock;
    int backLog;
    int maxWaitMs;
    HCkTaskW task;
    HCkOAuth2W oauth2;
    const wchar_t *startLine;
    const wchar_t *requestHeader;
    HCkStringBuilderW sbRequestBody;
    HCkStringBuilderW sbResponseHtml;
    HCkStringBuilderW sbResponse;
    HCkHashtableW hashTab;
    const wchar_t *idToken;
    HCkJwtW jwt;
    int leeway;
    BOOL bTimeValid;
    const wchar_t *payload;
    HCkJsonObjectW json;
    const wchar_t *joseHeader;
    HCkJsonObjectW jsonJoseHeader;
    HCkStringBuilderW sbJwks;
    HCkHttpW http;
    HCkJsonObjectW jwkset;
    const wchar_t *kid;
    HCkJsonObjectW jwk;
    BOOL verified;
    HCkPublicKeyW pubkey;

    success = FALSE;

    // This example requires the Chilkat API to have been previously unlocked.
    // See Global Unlock Sample for sample code.

    // In our previous example (Azure Fetch OpenID Connect metadata document) we fetched
    // the OpenID configuration document which is JSON which contains an entry for authorization_endpoint.

    authorization_endpoint = L"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize";

    // The OpenID Connect metadata document also contained a jwks_uri entry.  This is the JSON Web Key Set (JWKS),
    // which is a set of keys containing the public keys used to verify any JSON Web Token (JWT) (in this case the id_token)
    // issued by the authorization server and signed using the RS256 signing algorithm. 
    jwks_uri = L"https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys";

    // We're going to send the following GET request, but it will be sent through an interactive web browser (not by Chilkat).
    // The following code will form the URL that is to be programmatically loaded and sent in a browser.

    // GET https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
    // client_id=6731de76-14a6-49ae-97bc-6eba6914391e
    // &response_type=id_token
    // &redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
    // &response_mode=form_post
    // &scope=openid
    // &state=12345
    // &nonce=678910

    // Use this object to set params and then get the URL-encoded query params string 
    req = CkHttpRequestW_Create();
    CkHttpRequestW_AddParam(req,L"client_id",L"{client_id}");
    CkHttpRequestW_AddParam(req,L"response_type",L"id_token");
    CkHttpRequestW_AddParam(req,L"redirect_uri",L"http://localhost:3017/");
    CkHttpRequestW_AddParam(req,L"response_mode",L"form_post");
    CkHttpRequestW_AddParam(req,L"scope",L"openid");
    prng = CkPrngW_Create();
    CkHttpRequestW_AddParam(req,L"state",CkPrngW_genRandom(prng,3,L"decimal"));
    CkHttpRequestW_AddParam(req,L"nonce",CkPrngW_genRandom(prng,4,L"decimal"));

    encodedParams = CkHttpRequestW_getUrlEncodedParams(req);
    wprintf(L"%s\n",encodedParams);

    // Sample URL encoded params:
    // client_id=6731de76-14a6-49ae-97bc-6eba6914391e&redirect_uri=http%3A%2F%2Flocalhost%3A3017%2F&response_mode=form_post&scope=openid&state=3572902&nonce=57352474

    // This is the URL to be programmatically loaded and sent in an interactive web browser..
    sbUrl = CkStringBuilderW_Create();
    CkStringBuilderW_Append(sbUrl,authorization_endpoint);
    CkStringBuilderW_Append(sbUrl,L"?");
    CkStringBuilderW_Append(sbUrl,encodedParams);
    url = CkStringBuilderW_getAsString(sbUrl);

    // Before we launch the browser with the contents of sbUrl, create a socket to listen for the eventual callback..

    listenSock = CkSocketW_Create();

    // This is the connection received from the browser.
    browserSock = CkSocketW_Create();

    // Listen at the port indicated by the redirect_uri above.
    backLog = 5;
    success = CkSocketW_BindAndListen(listenSock,3017,backLog);
    if (success == FALSE) {
        wprintf(L"%s\n",CkSocketW_lastErrorText(listenSock));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        return;
    }

    // Wait for the browser's connection in a background thread.
    // (We'll send load the URL into the browser following this..)
    // Wait a max of 60 seconds before giving up.
    maxWaitMs = 30000;
    task = CkSocketW_AcceptNextAsync(listenSock,maxWaitMs,browserSock);
    CkTaskW_Run(task);

    // -------------------------------------------------------------------
    // At this point, your application should load the URL in a browser.
    oauth2 = CkOAuth2W_Create();
    success = CkOAuth2W_LaunchBrowser(oauth2,url);
    if (success == FALSE) {
        wprintf(L"%s\n",CkOAuth2W_lastErrorText(oauth2));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        return;
    }

    // -------------------------------------------------------------------

    // Wait for the listenSock's task to complete.
    success = CkTaskW_Wait(task,maxWaitMs);
    if (!success || (CkTaskW_getStatusInt(task) != 7) || (CkTaskW_getTaskSuccess(task) != TRUE)) {
        if (!success) {
            // The task.LastErrorText applies to the Wait method call.
            wprintf(L"%s\n",CkTaskW_lastErrorText(task));
        }
        else {
            // The ResultErrorText applies to the underlying task method call (i.e. the AcceptNextConnection)
            wprintf(L"%s\n",CkTaskW_status(task));
            wprintf(L"%s\n",CkTaskW_resultErrorText(task));
        }

        CkTaskW_Dispose(task);
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        return;
    }

    // If we get to this point, a connection on listenSock was accepted, and the redirected POST
    // is waiting to be read on the connected socket.
    // The POST we are going to read contains the following:

    // POST /myapp/ HTTP/1.1
    // Host: localhost
    // Content-Type: application/x-www-form-urlencoded
    // 
    // id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNB...&state=12345

    // But first.. we no longer need the listen socket...
    // Stop listening on port 3017.
    CkSocketW_Close(listenSock,10);

    CkTaskW_Dispose(task);

    // We're acting as a temporary web server to receive this one redirected HTTP request..
    // Read the start line of the request.. (i.e. the "POST /myapp/ HTTP/1.1")
    startLine = CkSocketW_receiveUntilMatch(browserSock,L"\r\n");
    if (CkSocketW_getLastMethodSuccess(browserSock) == FALSE) {
        wprintf(L"%s\n",CkSocketW_lastErrorText(browserSock));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        return;
    }

    // Read the request header.
    requestHeader = CkSocketW_receiveUntilMatch(browserSock,L"\r\n\r\n");
    if (CkSocketW_getLastMethodSuccess(browserSock) == FALSE) {
        wprintf(L"%s\n",CkSocketW_lastErrorText(browserSock));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        return;
    }

    wprintf(L"%s\n",requestHeader);
    wprintf(L"----\n");

    // Read the body.
    // The body will contain "id_token= eyJ......"
    sbRequestBody = CkStringBuilderW_Create();
    success = CkSocketW_ReceiveSb(browserSock,sbRequestBody);
    if (success == FALSE) {
        wprintf(L"%s\n",CkSocketW_lastErrorText(browserSock));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        CkStringBuilderW_Dispose(sbRequestBody);
        return;
    }

    wprintf(L"%s\n",CkStringBuilderW_getAsString(sbRequestBody));

    // Given that we're acting as a web server, we must send a response..
    // We can now send our HTTP response.
    sbResponseHtml = CkStringBuilderW_Create();
    CkStringBuilderW_Append(sbResponseHtml,L"<html><body><p>Thank you!</b></body</html>");

    sbResponse = CkStringBuilderW_Create();
    CkStringBuilderW_Append(sbResponse,L"HTTP/1.1 200 OK\r\n");
    CkStringBuilderW_Append(sbResponse,L"Content-Length: ");
    CkStringBuilderW_AppendInt(sbResponse,CkStringBuilderW_getLength(sbResponseHtml));
    CkStringBuilderW_Append(sbResponse,L"\r\n");
    CkStringBuilderW_Append(sbResponse,L"Content-Type: text/html\r\n");
    CkStringBuilderW_Append(sbResponse,L"\r\n");
    CkStringBuilderW_AppendSb(sbResponse,sbResponseHtml);

    CkSocketW_SendString(browserSock,CkStringBuilderW_getAsString(sbResponse));
    CkSocketW_Close(browserSock,50);

    // Get the id_token from the sbRequestBody that we just received.
    // (Remember, we're acting as the web server, thus we received the redirect request..)
    hashTab = CkHashtableW_Create();
    CkHashtableW_AddQueryParams(hashTab,CkStringBuilderW_getAsString(sbRequestBody));

    // See https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens#validating-an-id-token
    // for more information about ID tokens..
    idToken = CkHashtableW_lookupStr(hashTab,L"id_token");

    jwt = CkJwtW_Create();

    // Let's see if the time constraints, if any, are valid.
    // The above JWT was created on the afternoon of 16-May-2016, with an expiration of 1 hour.
    // If the current system time is before the "nbf" time, or after the "exp" time,
    // then IsTimeValid will return false/0.
    // Also, we'll allow a leeway of 60 seconds to account for any clock skew.
    // Note: If the token has no "nbf" or "exp" claim fields, then IsTimeValid is always true.
    leeway = 60;
    bTimeValid = CkJwtW_IsTimeValid(jwt,idToken,leeway);
    wprintf(L"time constraints valid: %d\n",bTimeValid);

    // Now let's recover the original claims JSON (the payload).
    payload = CkJwtW_getPayload(jwt,idToken);
    // The payload will likely be in compact form:
    wprintf(L"%s\n",payload);

    // We can format for human viewing by loading it into Chilkat's JSON object
    // and emit.
    json = CkJsonObjectW_Create();
    success = CkJsonObjectW_Load(json,payload);
    CkJsonObjectW_putEmitCompact(json,FALSE);
    wprintf(L"%s\n",CkJsonObjectW_emit(json));

    // Sample output:

    // {
    //   "aud": "f125d695-c50e-456e-a579-a486f06d1213",
    //   "iss": "https://login.microsoftonline.com/6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd/v2.0",
    //   "iat": 1626535322,
    //   "nbf": 1626535322,
    //   "exp": 1626539222,
    //   "aio": "AWQAm/8TAAAAHQncmY0VvhgyMIhfleHX3DjsGfmlPM1CopkJ3mPnBUnCxrJ0ubruaACEhwGO7NsoHBhqFEzRzPxOq7MtuGVFsql+qJKZx8vQCszKYEPX9Wb3b5+d5KJTABHCIH48bTFd",
    //   "idp": "https://sts.windows.net/9188040d-6c67-4c5b-b112-36a304b66dad/",
    //   "nonce": "1519043321",
    //   "rh": "0.ARgAZt2NbdFosEOvXOMbS33VzZXWJfEOxW5FpXmkhvBtEhMYALQ.",
    //   "sub": "RMIZlHJ7hfsJmL8Qq3h6M0nPi4g-HEavnAFgxzaT2KM",
    //   "tid": "6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd",
    //   "uti": "-BXGHxvfREW-r9HI5NBiAA",
    //   "ver": "2.0"
    // }

    // We can recover the original JOSE header in the same way:
    joseHeader = CkJwtW_getHeader(jwt,idToken);
    // The payload will likely be in compact form:
    wprintf(L"%s\n",joseHeader);

    // We can format for human viewing by loading it into Chilkat's JSON object
    // and emit.
    jsonJoseHeader = CkJsonObjectW_Create();
    success = CkJsonObjectW_Load(jsonJoseHeader,joseHeader);
    CkJsonObjectW_putEmitCompact(jsonJoseHeader,FALSE);
    wprintf(L"%s\n",CkJsonObjectW_emit(jsonJoseHeader));

    // Sample output:

    // {
    //   "typ": "JWT",
    //   "alg": "RS256",
    //   "kid": "nOo3ZDrODXEK1jKWhXslHR_KXEg"
    // }

    // Finally, we need to fetch the JSON Web Key Sets from the jwks_uri
    // and use it to verify the id_token's RSA signature.
    sbJwks = CkStringBuilderW_Create();
    http = CkHttpW_Create();
    success = CkHttpW_QuickGetSb(http,jwks_uri,sbJwks);
    if (success == FALSE) {
        wprintf(L"%s\n",CkHttpW_lastErrorText(http));
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        CkStringBuilderW_Dispose(sbRequestBody);
        CkStringBuilderW_Dispose(sbResponseHtml);
        CkStringBuilderW_Dispose(sbResponse);
        CkHashtableW_Dispose(hashTab);
        CkJwtW_Dispose(jwt);
        CkJsonObjectW_Dispose(json);
        CkJsonObjectW_Dispose(jsonJoseHeader);
        CkStringBuilderW_Dispose(sbJwks);
        CkHttpW_Dispose(http);
        return;
    }

    jwkset = CkJsonObjectW_Create();
    success = CkJsonObjectW_LoadSb(jwkset,sbJwks);
    CkJsonObjectW_putEmitCompact(jwkset,FALSE);
    wprintf(L"%s\n",CkJsonObjectW_emit(jwkset));

    // A sample jwkset:

    // {
    //   "keys": [
    //     {
    //       "kty": "RSA",
    //       "use": "sig",
    //       "kid": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
    //       "x5t": "nOo3ZDrODXEK1jKWhXslHR_KXEg",
    //       "n": "oaLLT9hkcSj ... NVrZdUdTBQ",
    //       "e": "AQAB",
    //       "x5c": [
    //         "MIIDBTC ... MRku44Dw7R"
    //       ],
    //       "issuer": "https://login.microsoftonline.com/6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd/v2.0"
    //     },
    //     {
    //       "kty": "RSA",
    //       "use": "sig",
    //       "kid": "l3sQ-50cCH4xBVZLHTGwnSR7680",
    //       "x5t": "l3sQ-50cCH4xBVZLHTGwnSR7680",
    //       "n": "sfsXMXW ... AYkwb6xUQ",
    //       "e": "AQAB",
    //       "x5c": [
    //         "MIIDBTCCA ... BWrh+/vJ"
    //       ],
    //       "issuer": "https://login.microsoftonline.com/6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd/v2.0"
    //     },
    //     {
    //       "kty": "RSA",
    //       "use": "sig",
    //       "kid": "DqUu8gf-nAgcyjP3-SuplNAXAnc",
    //       "x5t": "DqUu8gf-nAgcyjP3-SuplNAXAnc",
    //       "n": "1n7-nWSL ... V3pFWhQ",
    //       "e": "AQAB",
    //       "x5c": [
    //         "MIIC8TC ... 9pIcnkPQ=="
    //       ],
    //       "issuer": "https://login.microsoftonline.com/6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd/v2.0"
    //     },
    //     {
    //       "kty": "RSA",
    //       "use": "sig",
    //       "kid": "OzZ5Dbmcso9Qzt2ModGmihg30Bo",
    //       "x5t": "OzZ5Dbmcso9Qzt2ModGmihg30Bo",
    //       "n": "01re9a2BU ... 5OzQ6Q",
    //       "e": "AQAB",
    //       "x5c": [
    //         "MIIC8TC ... YmwJ6sDdRvQ=="
    //       ],
    //       "issuer": "https://login.microsoftonline.com/6d8ddd66-68d1-43b0-af5c-e31b4b7dd5cd/v2.0"
    //     }
    //   ]
    // }

    // We should have an RSA key with kid matching the kid from the joseHeader..
    kid = CkJsonObjectW_stringOf(jsonJoseHeader,L"kid");

    // Find the RSA key with the specified key id
    jwk = CkJsonObjectW_FindRecord(jwkset,L"keys",L"kid",kid,TRUE);
    if (CkJsonObjectW_getLastMethodSuccess(jwkset) == FALSE) {
        wprintf(L"Failed to find a matching RSA key in the JWK key set...\n");
        CkHttpRequestW_Dispose(req);
        CkPrngW_Dispose(prng);
        CkStringBuilderW_Dispose(sbUrl);
        CkSocketW_Dispose(listenSock);
        CkSocketW_Dispose(browserSock);
        CkOAuth2W_Dispose(oauth2);
        CkStringBuilderW_Dispose(sbRequestBody);
        CkStringBuilderW_Dispose(sbResponseHtml);
        CkStringBuilderW_Dispose(sbResponse);
        CkHashtableW_Dispose(hashTab);
        CkJwtW_Dispose(jwt);
        CkJsonObjectW_Dispose(json);
        CkJsonObjectW_Dispose(jsonJoseHeader);
        CkStringBuilderW_Dispose(sbJwks);
        CkHttpW_Dispose(http);
        CkJsonObjectW_Dispose(jwkset);
        return;
    }

    pubkey = CkPublicKeyW_Create();

    success = CkPublicKeyW_LoadFromString(pubkey,CkJsonObjectW_emit(jwk));
    if (success == FALSE) {
        wprintf(L"%s\n",CkPublicKeyW_lastErrorText(pubkey));
        wprintf(L"%s\n",CkJsonObjectW_emit(jwk));
    }
    else {
        verified = CkJwtW_VerifyJwtPk(jwt,idToken,pubkey);
        wprintf(L"Verified: %d\n",verified);
    }

    CkJsonObjectW_Dispose(jwk);


    CkHttpRequestW_Dispose(req);
    CkPrngW_Dispose(prng);
    CkStringBuilderW_Dispose(sbUrl);
    CkSocketW_Dispose(listenSock);
    CkSocketW_Dispose(browserSock);
    CkOAuth2W_Dispose(oauth2);
    CkStringBuilderW_Dispose(sbRequestBody);
    CkStringBuilderW_Dispose(sbResponseHtml);
    CkStringBuilderW_Dispose(sbResponse);
    CkHashtableW_Dispose(hashTab);
    CkJwtW_Dispose(jwt);
    CkJsonObjectW_Dispose(json);
    CkJsonObjectW_Dispose(jsonJoseHeader);
    CkStringBuilderW_Dispose(sbJwks);
    CkHttpW_Dispose(http);
    CkJsonObjectW_Dispose(jwkset);
    CkPublicKeyW_Dispose(pubkey);

    }