DataFlex
DataFlex
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 DataFlex Downloads
Use ChilkatAx-win32.pkg
Procedure Test
Boolean iSuccess
String sAuthorization_endpoint
String sJwks_uri
Handle hoReq
Handle hoPrng
String sEncodedParams
Handle hoSbUrl
String sUrl
Handle hoListenSock
Variant vBrowserSock
Handle hoBrowserSock
Integer iBackLog
Integer iMaxWaitMs
Variant vTask
Handle hoTask
Handle hoOauth2
String sStartLine
String sRequestHeader
Variant vSbRequestBody
Handle hoSbRequestBody
Variant vSbResponseHtml
Handle hoSbResponseHtml
Handle hoSbResponse
Handle hoHashTab
String sIdToken
Handle hoJwt
Integer iLeeway
Boolean iBTimeValid
String sPayload
Handle hoJson
String sJoseHeader
Handle hoJsonJoseHeader
Variant vSbJwks
Handle hoSbJwks
Handle hoHttp
Handle hoJwkset
String sKid
Variant vJwk
Handle hoJwk
Boolean iVerified
Variant vPubkey
Handle hoPubkey
String sTemp1
Integer iTemp1
Boolean bTemp1
Move False To iSuccess
// 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.
Move "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize" To sAuthorization_endpoint
// 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.
Move "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys" To sJwks_uri
// 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
Get Create (RefClass(cComChilkatHttpRequest)) To hoReq
If (Not(IsComObjectCreated(hoReq))) Begin
Send CreateComObject of hoReq
End
Send ComAddParam To hoReq "client_id" "{client_id}"
Send ComAddParam To hoReq "response_type" "id_token"
Send ComAddParam To hoReq "redirect_uri" "http://localhost:3017/"
Send ComAddParam To hoReq "response_mode" "form_post"
Send ComAddParam To hoReq "scope" "openid"
Get Create (RefClass(cComChilkatPrng)) To hoPrng
If (Not(IsComObjectCreated(hoPrng))) Begin
Send CreateComObject of hoPrng
End
Get ComGenRandom Of hoPrng 3 "decimal" To sTemp1
Send ComAddParam To hoReq "state" sTemp1
Get ComGenRandom Of hoPrng 4 "decimal" To sTemp1
Send ComAddParam To hoReq "nonce" sTemp1
Get ComGetUrlEncodedParams Of hoReq To sEncodedParams
Showln sEncodedParams
// 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..
Get Create (RefClass(cComChilkatStringBuilder)) To hoSbUrl
If (Not(IsComObjectCreated(hoSbUrl))) Begin
Send CreateComObject of hoSbUrl
End
Get ComAppend Of hoSbUrl sAuthorization_endpoint To iSuccess
Get ComAppend Of hoSbUrl "?" To iSuccess
Get ComAppend Of hoSbUrl sEncodedParams To iSuccess
Get ComGetAsString Of hoSbUrl To sUrl
// Before we launch the browser with the contents of sbUrl, create a socket to listen for the eventual callback..
Get Create (RefClass(cComChilkatSocket)) To hoListenSock
If (Not(IsComObjectCreated(hoListenSock))) Begin
Send CreateComObject of hoListenSock
End
// This is the connection received from the browser.
Get Create (RefClass(cComChilkatSocket)) To hoBrowserSock
If (Not(IsComObjectCreated(hoBrowserSock))) Begin
Send CreateComObject of hoBrowserSock
End
// Listen at the port indicated by the redirect_uri above.
Move 5 To iBackLog
Get ComBindAndListen Of hoListenSock 3017 iBackLog To iSuccess
If (iSuccess = False) Begin
Get ComLastErrorText Of hoListenSock To sTemp1
Showln sTemp1
Procedure_Return
End
// 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.
Move 30000 To iMaxWaitMs
Get pvComObject of hoBrowserSock to vBrowserSock
Get ComAcceptNextAsync Of hoListenSock iMaxWaitMs vBrowserSock To vTask
If (IsComObject(vTask)) Begin
Get Create (RefClass(cComChilkatTask)) To hoTask
Set pvComObject Of hoTask To vTask
End
Get ComRun Of hoTask To iSuccess
// -------------------------------------------------------------------
// At this point, your application should load the URL in a browser.
Get Create (RefClass(cComChilkatOAuth2)) To hoOauth2
If (Not(IsComObjectCreated(hoOauth2))) Begin
Send CreateComObject of hoOauth2
End
Get ComLaunchBrowser Of hoOauth2 sUrl To iSuccess
If (iSuccess = False) Begin
Get ComLastErrorText Of hoOauth2 To sTemp1
Showln sTemp1
Procedure_Return
End
// -------------------------------------------------------------------
// Wait for the listenSock's task to complete.
Get ComWait Of hoTask iMaxWaitMs To iSuccess
Get ComStatusInt Of hoTask To iTemp1
Get ComTaskSuccess Of hoTask To bTemp1
If (Not iSuccess Or (iTemp1 <> 7) Or (bTemp1 <> True)) Begin
If (Not iSuccess) Begin
// The task.LastErrorText applies to the Wait method call.
Get ComLastErrorText Of hoTask To sTemp1
Showln sTemp1
End
Else Begin
// The ResultErrorText applies to the underlying task method call (i.e. the AcceptNextConnection)
Get ComStatus Of hoTask To sTemp1
Showln sTemp1
Get ComResultErrorText Of hoTask To sTemp1
Showln sTemp1
End
Send Destroy of hoTask
Procedure_Return
End
// 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.
Get ComClose Of hoListenSock 10 To iSuccess
Send Destroy of hoTask
// 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")
Get ComReceiveUntilMatch Of hoBrowserSock (character(13)) + (character(10)) To sStartLine
Get ComLastMethodSuccess Of hoBrowserSock To bTemp1
If (bTemp1 = False) Begin
Get ComLastErrorText Of hoBrowserSock To sTemp1
Showln sTemp1
Procedure_Return
End
// Read the request header.
Get ComReceiveUntilMatch Of hoBrowserSock (character(13)) + (character(10)) + (character(13)) + (character(10)) To sRequestHeader
Get ComLastMethodSuccess Of hoBrowserSock To bTemp1
If (bTemp1 = False) Begin
Get ComLastErrorText Of hoBrowserSock To sTemp1
Showln sTemp1
Procedure_Return
End
Showln sRequestHeader
Showln "----"
// Read the body.
// The body will contain "id_token= eyJ......"
Get Create (RefClass(cComChilkatStringBuilder)) To hoSbRequestBody
If (Not(IsComObjectCreated(hoSbRequestBody))) Begin
Send CreateComObject of hoSbRequestBody
End
Get pvComObject of hoSbRequestBody to vSbRequestBody
Get ComReceiveSb Of hoBrowserSock vSbRequestBody To iSuccess
If (iSuccess = False) Begin
Get ComLastErrorText Of hoBrowserSock To sTemp1
Showln sTemp1
Procedure_Return
End
Get ComGetAsString Of hoSbRequestBody To sTemp1
Showln sTemp1
// Given that we're acting as a web server, we must send a response..
// We can now send our HTTP response.
Get Create (RefClass(cComChilkatStringBuilder)) To hoSbResponseHtml
If (Not(IsComObjectCreated(hoSbResponseHtml))) Begin
Send CreateComObject of hoSbResponseHtml
End
Get ComAppend Of hoSbResponseHtml "<html><body><p>Thank you!</b></body</html>" To iSuccess
Get Create (RefClass(cComChilkatStringBuilder)) To hoSbResponse
If (Not(IsComObjectCreated(hoSbResponse))) Begin
Send CreateComObject of hoSbResponse
End
Get ComAppend Of hoSbResponse "HTTP/1.1 200 OK" + (character(13)) + (character(10)) To iSuccess
Get ComAppend Of hoSbResponse "Content-Length: " To iSuccess
Get ComLength Of hoSbResponseHtml To iTemp1
Get ComAppendInt Of hoSbResponse iTemp1 To iSuccess
Get ComAppend Of hoSbResponse (character(13)) + (character(10)) To iSuccess
Get ComAppend Of hoSbResponse "Content-Type: text/html" + (character(13)) + (character(10)) To iSuccess
Get ComAppend Of hoSbResponse (character(13)) + (character(10)) To iSuccess
Get pvComObject of hoSbResponseHtml to vSbResponseHtml
Get ComAppendSb Of hoSbResponse vSbResponseHtml To iSuccess
Get ComGetAsString Of hoSbResponse To sTemp1
Get ComSendString Of hoBrowserSock sTemp1 To iSuccess
Get ComClose Of hoBrowserSock 50 To iSuccess
// 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..)
Get Create (RefClass(cComChilkatHashtable)) To hoHashTab
If (Not(IsComObjectCreated(hoHashTab))) Begin
Send CreateComObject of hoHashTab
End
Get ComGetAsString Of hoSbRequestBody To sTemp1
Get ComAddQueryParams Of hoHashTab sTemp1 To iSuccess
// See https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens#validating-an-id-token
// for more information about ID tokens..
Get ComLookupStr Of hoHashTab "id_token" To sIdToken
Get Create (RefClass(cComChilkatJwt)) To hoJwt
If (Not(IsComObjectCreated(hoJwt))) Begin
Send CreateComObject of hoJwt
End
// 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.
Move 60 To iLeeway
Get ComIsTimeValid Of hoJwt sIdToken iLeeway To iBTimeValid
Showln "time constraints valid: " iBTimeValid
// Now let's recover the original claims JSON (the payload).
Get ComGetPayload Of hoJwt sIdToken To sPayload
// The payload will likely be in compact form:
Showln sPayload
// We can format for human viewing by loading it into Chilkat's JSON object
// and emit.
Get Create (RefClass(cComChilkatJsonObject)) To hoJson
If (Not(IsComObjectCreated(hoJson))) Begin
Send CreateComObject of hoJson
End
Get ComLoad Of hoJson sPayload To iSuccess
Set ComEmitCompact Of hoJson To False
Get ComEmit Of hoJson To sTemp1
Showln sTemp1
// 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:
Get ComGetHeader Of hoJwt sIdToken To sJoseHeader
// The payload will likely be in compact form:
Showln sJoseHeader
// We can format for human viewing by loading it into Chilkat's JSON object
// and emit.
Get Create (RefClass(cComChilkatJsonObject)) To hoJsonJoseHeader
If (Not(IsComObjectCreated(hoJsonJoseHeader))) Begin
Send CreateComObject of hoJsonJoseHeader
End
Get ComLoad Of hoJsonJoseHeader sJoseHeader To iSuccess
Set ComEmitCompact Of hoJsonJoseHeader To False
Get ComEmit Of hoJsonJoseHeader To sTemp1
Showln sTemp1
// 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.
Get Create (RefClass(cComChilkatStringBuilder)) To hoSbJwks
If (Not(IsComObjectCreated(hoSbJwks))) Begin
Send CreateComObject of hoSbJwks
End
Get Create (RefClass(cComChilkatHttp)) To hoHttp
If (Not(IsComObjectCreated(hoHttp))) Begin
Send CreateComObject of hoHttp
End
Get pvComObject of hoSbJwks to vSbJwks
Get ComQuickGetSb Of hoHttp sJwks_uri vSbJwks To iSuccess
If (iSuccess = False) Begin
Get ComLastErrorText Of hoHttp To sTemp1
Showln sTemp1
Procedure_Return
End
Get Create (RefClass(cComChilkatJsonObject)) To hoJwkset
If (Not(IsComObjectCreated(hoJwkset))) Begin
Send CreateComObject of hoJwkset
End
Get pvComObject of hoSbJwks to vSbJwks
Get ComLoadSb Of hoJwkset vSbJwks To iSuccess
Set ComEmitCompact Of hoJwkset To False
Get ComEmit Of hoJwkset To sTemp1
Showln sTemp1
// 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..
Get ComStringOf Of hoJsonJoseHeader "kid" To sKid
// Find the RSA key with the specified key id
Get ComFindRecord Of hoJwkset "keys" "kid" sKid True To vJwk
If (IsComObject(vJwk)) Begin
Get Create (RefClass(cComChilkatJsonObject)) To hoJwk
Set pvComObject Of hoJwk To vJwk
End
Get ComLastMethodSuccess Of hoJwkset To bTemp1
If (bTemp1 = False) Begin
Showln "Failed to find a matching RSA key in the JWK key set..."
Procedure_Return
End
Get Create (RefClass(cComChilkatPublicKey)) To hoPubkey
If (Not(IsComObjectCreated(hoPubkey))) Begin
Send CreateComObject of hoPubkey
End
Get ComEmit Of hoJwk To sTemp1
Get ComLoadFromString Of hoPubkey sTemp1 To iSuccess
If (iSuccess = False) Begin
Get ComLastErrorText Of hoPubkey To sTemp1
Showln sTemp1
Get ComEmit Of hoJwk To sTemp1
Showln sTemp1
End
Else Begin
Get pvComObject of hoPubkey to vPubkey
Get ComVerifyJwtPk Of hoJwt sIdToken vPubkey To iVerified
Showln "Verified: " iVerified
End
Send Destroy of hoJwk
End_Procedure