Category Archives: Uncategorized

Passwordless = secretless ?

Passwordless = secretless ?
Lately I have been seeing more writing on how pernicious password are, than usual. Not all of which by vendors of passwordless solutions. These vendors’ product fall in the categories password-vaults, with or without browser plugins; or Single Sign-on solutions , with or without user federation. Sometimes coupled with biometrics-based authentication.
Why this recent rise in such writing should have happened, is not hard to figure. With home offices during the pandemic, users have had more experience with passwords in a corporate setting – and tech support have been correspondingly more strained by it.
That the use of passwords have problems need not be laboured. It does.
The only mercy on that front is that finally the virtue of frequently changing passwords have been seriously questioned. The more often something is changed, the more likely it is to be written down, or be derivative of past password or well-known information. This is obvious to anyone who has passwords, but apparently not to those writing policy on their management.
Complex and unique passwords rarely changed, are better than derivative passwords often changed.
What’s the alternative ? SSO – you login once and the session is essentially reused where ever needed; mitigates the problem, but can be a bear to setup. Federation: you login once and the ID itself is shifted around; Very secure but even more work to set up. Password vaults? Default in browser; weak security. Plugin with off-browser storage; Can be quite nice but still password protected. And neither of the above support off-browser logins.
Having set up plenty of SSO and federation solutions in my time, I am no stranger to what an ordeal such implementations can be. Password-vaults in browsers are a lot easier in this regard. Yet they still have passwords since the user’s accounts have them. If letting your browser store your password is not “on”, there are plugin solutions that do the same thing and store that password centrally (Okta is an example). Not all applications are browser-based of course. So you have stand alone password vaults that lets you store and retrieve at will. But those vaults have passwords themselves (which at least addresses the problem with browser’s default password vault – no protection). So the problem of passwords just shifted.

Enter biometrics. Not As Bad As It Used To Be, is the phrase that most commonly follows any mention of biometrics. Face scanning is no longer just for Bond villains. Regular people do it too. Finger print scanning is more reliable, as in actually allowing you access, but has been regularly fooled by photographs of fingers. Or actual fingers, as seen in any number of movies. Not that getting out the bolt cutter is needed. Getting hold of the fingerprints of a person is trivially easy. Face even more so. Assuming the scanner is not fooled by a mere picture held up in front of it, 3D printing will get the job done. 3D printing a face from a mere picture ? Ever heard of a stereoscope; Older than the cinematograph. Of course 3D printing a face from some sneakily obtained pictures is a serious commitment. And at the moment doable only for dedicated specialists. There will be an app for that within 5 years.
Ever notice that the cases appearing in the press over the last decade or so , where law enforcement officials had difficulties accessing a persons electronic devices. None of these devices were protected by biometrics. Ever wonder why?
Passwords are secrets. In the crypto literature they are not even called passwords but rather secrets. Something only you know. The provision of which proves you are you. Biometrics are by definition not secret. Obscure perhaps, but never secret. Law-enforcement loves biometrics. As well they should.
Fundamentally, replacing password with biometrics is like saying that you have no true secrets.
Let me flesh that last bit out a little. Secret is not a scalar quantity as they say in mathematics. There is more to it. A vector, as it were. Specifically: secret from whom is that extra dimension.
To say you have a secret it to say you have a secret from someone, possibly everyone. It is fundamentally a statement defining a group of persons: Those who have access to the secret. Only one person perhaps – you.
Password security is knowledge based and biometrics security is object based. And objects are not secret in the way knowledge can be. Access to those objects can not be controlled in the way access to knowledge can be. The group with access to the object is never of one.
Does this mean biometric authentication is a red herring. Or worse. No. Access control is a risk management implementation. There various authentication mechanisms are selected to match the level of identity assurance required. But to say that biometrics are always more secure than passwords it to overlook it’s intrinsic weakness: it isn’t secret.

A new, even-lower from Facebook aka. deception where you most expect it

A new, even-lower from Facebook aka. deception where you most expect it

When you post a link on Facebook , FB does a retrieval from where the link points to and sets up a little display of what that is. A small picture and some text collected from the page. To show you what’s at the end of the URL

And also shows the URL in full. This is where you are supposed to click. Those with some HTML knowledge, know that what is actually used by the browser when going to a link is not the text displayed on the page, which is meant to be human readable (and is contained inside the “a”-tags in the html) , but something else buried inside the markup (in the href attribute of the a-tag.  )

Most browsers show this true URL in the small bar at the bottom of the page when you hover the mouse of the URL. So you can see whare you are going before you click. Everyone should always se where they are about to go, before clicking. This is why I loath and fear link shorteners such as, mush used by among others Linkedin (yes, Linkedin I am looking at you. Stop, stop, stop, stop doing that! Just Stop It !). since this makes looking at the real URL of a link before clicking pointless.

Enter Facebook. Trust them to go even lower. What they have done is to insert some javascript that shows the URL where you will end up on the browser bar when you hover over the link. But that is not where you go. You go to Facebook along with a tracking cookie. Then Facebook forwards you to your destination (The URL show on the page and on the browser bar). Along with yet another tracking cookie.

An example. Someone sent me a link in FB.

https : //

This is the link show in the bowser bar and is where you’d have reason to think you are going.

But this is where you actually go

https : //]-R&c[0]=AT0ZA5Vg7zjuFtOFS7aRmqMPT-xnxv6lOAxQm5SgwNAfkvX8d8Her7MMmIUgEl3R7P7QAinPyU-rhi67C_Yhfbdn2jc3-3xtWgZQqGRCg7-_Kob-6kWjvRVHQpTwly3y5efDT9xme_Z-MT8GR1xn

To see this value, slink use the “copy link address” in the right-click context menu on the link.

Breaking to down:

Yep, were off to facebook alright.

https : / / l . facebook . com / l.php?

And here is where we were told we’re going.

and here is the tracking cookie




There we have it. Facebook, when clicked takes them to Facebook first.

Not altogether unrelated. Facebook also take other steps to prevent users from ferreting out what they are up to. Try switching on the web console on you browser and Facebook send instructions to you browser to switch it off. Yes, repeating that last bit. FB actually sends JavaScript to your browser to kill the web console if you have it running in your browser. That is some shit.  The redirect is not through the HTTP 403 but rather trough JavaScript in the html page being sent. That JavaScript kills the webconsole as well as directing your browser to the desired destination. And setting some cookies.

You can switch on the webconsole again and if you do you are presented with the following message.

Sorry, that is in Swedish (I am behind a proxy)They claim , rightly, that this tool you are seeing this message in is for developers.

Of course, I still have plugins such as HTTP Header Live and HTTP-Tracker which FB can’t touch.

So FB are not only actively trying to deceive their users about what they are doing, they are also taking direct steps to prevent that deception from being uncovered.

Sorry, that is in Swedish (I am behind a proxy)They claim , rightly, that this tool you are seeing this message in is for developers.

Of course, I still have plugins such as HTTP Header Live and HTTP-Tracker which FB can’t touch.

So FB are not only actively trying to deceive their users about what they are doing, they are also taking direct steps to prevent that deception from being uncovered.

Want to use quantum technology for encryption, right now ?

And don’t have access to any of this technology ?

https :// /story/why-this-intercontinental-quantum-encrypted-video-hangout-is-a-big-deal/?mbid=BottomRelatedStories

OK, the below will not be using quantum signals to agree encryption keys like those guys are doing.

But rather a far simpler simpler quantum randomness beacon as a source of entropy from which the symmetric encryption key is created.

Symmetric encryption systems, like AES256, are all vulnerable to their keys being insufficiently random. They lack entropy in the jargon. Here is an example for using quantum-derived randomness and use it to encrypt some data with the AES256 algorithm.

First get the random data from the quantum source (and make sure this is done a fresh every time this procedure is followed.

To point out the obvious: the source is publicly available and it being used as a source of entropy is therefore somewhat predictable.

The genuinely random data from multiple sources are therefore blended in a psuedo random-way. Genuinely random data, mixed with genuinely random data, even if being mixed only in psuedo-random way, is still genuinely random data.

Get two different sets of entropy from source

Using this source, twice:

http ://

curl -X GET “” > 1.random

sleep 3

curl -X GET “” > 2.random

Randomly mix together the two random strings collected from the source

1) split the entropy files into multiple lines and have each line start with a psuedo random number

2) paste the resulting lists together and sort on the leading number.

3) remove the numbers and line breaks.

cat <(cat 1.random | sed ‘s/\(.\)/\1 /g’| awk ‘BEGIN{RS=” “;FS=”\n”;ORS=”\n”;OFS=”\n”}{print rand() ” ” $0 }’) <(cat 2.random | sed ‘s/\(.\)/\1 /g’| awk ‘BEGIN{RS=” “;FS=”\n”;ORS=”\n”;OFS=”\n”}{print rand() ” ” $0 }’) | sort | awk ‘BEING{FS=” “;ORS=””;OFS=””}{print $2}’ | tr -d ‘\n’ > 3.random

OK, now we have some properly random data. Use this to create the key and iv for the AES encryption.

Take the first 64 hex characters starting from the 2nd (picked 2nd at random, another starting point can be used) character and make the AES256 key

KEY=$(cat 3.random | awk ‘BEGIN{ORS=””;OFS=””}{print toupper(substr($0,2,64)) }’ | tr -d ‘\n’ | tr -d ‘\r’)

Take the first 32 hex characters starting from the 80th (picked 80 at random, another starting point can be used) character and make the initialization vector

IV=$(cat 3.random | awk ‘BEGIN{ORS=””;OFS=””}{print toupper(substr($0,80,32)) }’ | tr -d ‘\n’ | tr -d ‘\r’ )

Create a test message file to encrypt.

echo “test message” > message.txt

Encrypt the message file. not using any salt here since the key will not be reused.

openssl enc -nosalt -aes-256-cbc -in message.txt -out message.txt.enc -base64 -K $KEY -iv $IV


openssl enc -nosalt -aes-256-cbc -d -in message.txt.enc -base64 -K $KEY -iv $IV

Ok, a valid AES key was created.

Let’s see about a more common problem: Generating an RSA public-private keypair for use in X509 certificates. The use of properly random data in generating RSA keypair is usually neglected (see any number of openssl cookbook example for generating publickey keypairs to satisfy this point).

openssl genrsa -des3 -rand 3.random -out quantum-entropy-keypair.privkey 2048

Where “3.random” is the file generated above.

Obviously you should add options for encrypting the private key with tripple-des (the “-des3” option)

When prompted for the password to protect the private key, don’t undo the good work with quantum data by selecting a weak password. This password is used to derive the encryption key (triple DES in the example) and rather illustrates the key space vulnerability of all encryption systems – none are more secure than their passwords/passphrases.

Do you have it ? Really ?

What we are looking for the the probability of a person having Corona given a positive test and the test is 90% accurate.

For brevity:

A = have Corona
B = have positive test

P(A) = 0,01 – base rate of contagion in the population. And by implication P(not-A) = 0,99

Test is 90% accurate, and let us further suppose that this is both the sensitivity and the specificity. Where sensitivity is about the absence of false negatives – are you finding it all ? – the proportion of actual positives that are correctly identified as such. And Specificity is about the absence of false positives – are you detecting something else ? – the proportion of actual negatives that are correctly identified as such. Ideally both should be high, but typically one will have to be traded off against the other in any real test. Both being 90% is unrealisitc.

For short P(B|A) = 0.9 and P(not-B | not-A) = 0.9

From this we use the fact that conditional probabilities add up to 1

i.e. P ( B|A ) + P(not-B|A) = 1

and get P(not-B | A) = 0.1 and P( B | not-A) = 0.1

Bayes’ Theorem stipulate P(A|B) = P(B|A) * P(A) / P(B)

Where P(B) = P(B,A) + P(B,not-A) = P(B|A) * P(A) + P(B | not-A) * P(not-A)

Putting in the numbers from above:

P(A|B) = ( 0,9 * 0,01) / ( 0,9 * 0,01 + 0,1 * 0,99) = 0,009 / ( 0,009 + 0,099) ~ 10%

In plan language: The probability of actually having the Corona given a positive test with a 90% accuracy, is only about 10%


Let’s recast: what is the chance of not having Corona given the test say you don’t. In other words: how reliable is the all-clear ?

For brevity: What is P ( not-A | not-B) ?

Bayes’ says P(A|B) = P(B|A) * P(A) / P(B)

which for this purpose becomes:

P(not-A| not-B) = P(not-B| not-A) * P(not-A) / P(not-B)

Where P(not-B) = P(not-B, not-A) + P(not-B, A) = P(not-B| not-A) * P(not-A) + P(not-B | A) * P(A)

All of these numbers we have

P(not-A| not-B) = 0,9 * 0,99 / ( 0,9 * 0,99 + 0,1 * 0,01 ) ~ 99,9%

Which is rather impressive. The all-clear really is all-clear. No means no.

Business and reuptational damage from shoddy security pratices at subcontractors. (Norwegian)

I en tid har jeg hatt Telenor Bedrift som mobil leverandør. Men nå har jeg sluttet med det.

Grunnet en heller uklar fakturerings model ble noen av fakturaene fra Telenor avvist for å overstige beløps grensen. Og gikk videre til inkasso. So far, so good. Her ble det interessant. Inkasso firmaet var for meg en helt ukjent firma, Gothia Group, GG. Som sendte en epost med henstilling å betale til KTO nr og KID nummer. De hevdet å agere på vegne av Telenor (merk: ikke «Telenor Bedrift», som jeg har mitt kundeforhold hos). Gitt at en betydelig andel av Norges befolkning har et kunde forhold til Telenor er en slik generisk påstand en ganske god start på et masse-phishing angrep.

Eposten inneholdt heller lite (intet) som forutsatte detaljert kjennskap til kunde forholdet hos Telenor: Ikke opprinnelig faktura nummer eller kunde nummer. Dette ble senere fulgt opp med en oppringning som likeledes var blottet for spesifikk informasjon. Stemmen på telefonen hadde ikke noen tydelig russisk aksent men ellers kunne alt sammen vært generert i en russisk hacker fabrikk. Men gitt av en del call-senter oppgaver er flyttet ut av landet, er ikke en aksent umiddelbart mistenkelig heller. Mangel på spesifikk informasjon derimot er det.

Etter å ha sjekket opp mailen med Telenor gikk jeg inn på den i eposten oppgitte nettsiden.

Starten var mindre enn tillitts vekkedne. GG sine nettsider har ikke sine digitale sertifikater satt opp på en måte som må anses som minste kravet til en virksomhet som driver med penger.

Spesifikt: de benytter Let’s Encrypt Authority X3 som CA for sertifikatet på Dette er IKKE en forsvarlig utsteder av digital sertifikater for formålet å identifisere organisasjoners nettsteder. Noe Let’s Encrypt selv heller ikke påstår.

Om penger er involvert er det ikke urimelig at organisasjonen bak nettstedet faktisk identifiserer seg overfor brukerens nettleser.

Men i det minste følger GG godpraksis i at kunden må logg seg inn for å finne korrekt betalings informasjon: En epost kan som kjent komme fra hvor som helst. Den sikkre informasjonen er den kunden selv finner frem til.

Men dette gjelder ikke for Telenor Bedrifts kunder. Den eneste innloggings mekanismen GG har er BankID. Og det virker som de ikke har noen integrasjon som gjøre at de kan koble den personlige innloggingen i BankID med en organisasjon. Så bedrifts kunder kan ikke logge seg inn hos GG for å hente betalings detaljer. De må få dette på epost, etter oppfordring. Eposter som ikke inneholder noen informasjon som kobler kravet til opprinnelig faktura fra Telenor Bedrift. Intet i den er hentet fra Telenor eller er av en slik art at ikke hvem som helst vet det. En russisk «troll farm» ville kunne sendt den eksakt samme eposten.

Så Telenor benytter Gothia Group som ikke kan håndtere bedrifts kunder, for sine bedrifts kunder.

Jeg er ikke lengre en kunde av Telenor Bedrift.

federated rigamarole (dual Norwegian and English)

Hvor mange nettsteder er man innom dersom man logger seg inn på NAV med BankId. Er det fler enn 10 ?
Det rette antallet er faktisk 14, spred over 7 domener. I disse GDPR tider kan det være instruktivt å se hvem som følger med på deg når du logger inn på NAV.

Gå til https :// www . nav . no/ og slå på Web Console før du starter med inloggingen , så får du se. Jeg gjorde dette, lagret resultatet til en HAR-fil og gjorde noen enkle uttrekk fra denne.

egrep “\”url\”:” HAR-file | sed ‘s/.*”url”: “htt[^\/]*\/\///’ | sed ‘s/\/.*//’ | sed ‘s/.*\.\([^\.]*\.[^\.]*\)$/\1/’ | sort | uniq -c | sort -r


We’re definitely at, with a total of 61 calls there. is as expected; And is no surprice as the government federation gateway (55 calls).
Men så blir det interessant. De 3 nederste på listen er noe overaskende. Når det det nødvendig å koble in Microsoft for en pålogging til NAV ?
Det er heller ikke åpenbart at Google trenger å vite når noen innom NAV. Selv om det nok er kunder av Google Analytics som gjerne kunne tenke seg å vite det.

But who are making these quiestioanable calls ?
Going back to the HAR-file.

egrep -v “text\”:” HAR-file | tr -d “\n” | sed ‘s/”url”:/\n “url\”:/g’ | sed ‘s/ */ /g’ | egrep “Referer” | sed ‘s/”url”: “\([^\”]*\)”.*{ “name”: “Referer”, “value”: “\([^\”]*\).*/\2 \1 /g’ | sed ‘s/ [^/]*:\/\/\([^/]*\)\/[^ ]* [^/]*:\/\/\([^/]*\)\/[^ ]*/\1 \2/’ | sort | uniq

The culprit is NAV itself. It is their web application which is making calls to Google.
The calls to, and are all only from NAV

The microsoft call is made by Difi too. So let’s look at that first.

The following command extracts the domain call and redirect sequence. (HTTP 302 redirects)

egrep -v “text\”:” HAR-file | tr -d “\n” | sed ‘s/”url”:/\n “url\”:/g’ | sed ‘s/ */ /g’ | egrep “\”status\”: 302″ | sed ‘s/\(\”url\”: \”[^\”]*\”\).*\”Location\”, \”value\”: \(\”[^\”]*\).*/\n\n\n\1\n\n\”Location\”: \2/’

Showing the url being called and the URL being redirected too (the Location HTTP header sent back to the browser from the site along with the HTTP 302 code) when calling that URL.

“url”: “;
“Location”: “

“url”: “;
“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “


“url”: “;

“Location”: “


“url”: “;

“Location”: “

“url”: “;

“Location”: “


“url”: “;

“Location”: “


“url”: “;

“Location”: “


So the sequence is from NAV to Microsoft to Difi and then reversing the steps back again. Which is how Micorsoft is called by both Difi and NAV.

Where are the calls to, and from, and more particularly what are they?

egrep -v “text\”:” HAR-file | tr -d “\n” | sed ‘s/”url”:/\n “url\”:/g’ | sed ‘s/ */ /g’ | egrep “Referer” | sed ‘s/”url”: “\([^\”]*\)”.*{ “name”: “Referer”, “value”: “\([^\”]*\).*/\2 \1 /g’ | egrep “(||”

Some things here are not necessarily ok. has “nav” as a subdomain and the calls go here. Apparently with session specific information. Which is user specific information. Where is it going ?

This command will get the registration info on the domain

curl ‘; -H ‘Content-type: application/x-www-form-urlencoded;charset=UTF-8’ –data “execute=getLocation&isip=false&$(( $(date +%s)-10))” | sed ‘s/#:#/\n/g’

Falkenberg in Sweden. Well, that might be ok geographically. But it is not inside Norway, and NAV is a goverment agency.

Google is in California, but their datacenters are all over. gives a Dublin address, again that can mean anything.
Though in either case the location to which data is being sent is not likely in Norway.

Let’s look at some of that data. Are there any POST calls? HTTP POSTs have the highest potential for carrying off data (tough cookies can carry plenty too )

cat HAR-file | tr -d “\n” | sed ‘s/”url”:/\n “url\”:/g’ | sed ‘s/ */ /g’ | egrep -i “\”method\”: \”POST\”” | egrep -v “^ \”url\”: \”https://%5B^/]*.(bankid|nav|difi).no”

“url”: “;, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0” }, { “name”: “Accept”, “value”: “application/json” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “Content-Type”, “value”: “text/plain; charset=utf-8” }, { “name”: “Content-Length”, “value”: “724” }, { “name”: “Origin”, “value”: “; }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Referer”, “value”: “; }, { “name”: “Cookie”, “value”: “vngage.srvid=da46b3656037eb53” } ], “cookies”: [ { “name”: “vngage.srvid”, “value”: “da46b3656037eb53” } ], “queryString”: [ { “name”: “json”, “value”: “true” }, { “name”: “sessionId”, “value”: “f1f43342-d73f-4242-aaad-cc5a951d83b7 Fogm7TkHIHjpVxuZ4w0e6KDH9p2WZkmphcX6U9L4=” } ], “headersSize”: 552, “postData”: { “mimeType”: “text/plain; charset=utf-8”, “params”: [], “text”: “{\”items\”:[{\”contentHeaders\”:{\”Content-Type\”:\”application/json\”},\”method\”:\”post\”,\”uri\”:\”\”,\”body\”:\”[{\\\”type\\\”:\\\”Navigation\\\”,\\\”url\\\”:\\\”\\\”,\\\”referrer\\\”:\\\”\\\”,\\\”visitId\\\”:\\\”00000000-0000-0000-0000-000000000000\\\”,\\\”siteId\\\”:\\\”1F1046B2-16A5-40A1-AD72-65B34BA29159\\\”,\\\”metaData\\\”:[{\\\”property\\\”:\\\”triggerType\\\”,\\\”content\\\”:\\\”pageload\\\”}]},{\\\”type\\\”:\\\”Opportunity\\\”,\\\”visitId\\\”:\\\”00000000-0000-0000-0000-000000000000\\\”,\\\”siteId\\\”:\\\”1F1046B2-16A5-40A1-AD72-65B34BA29159\\\”,\\\”opportunityId\\\”:\\\”615FF5E7-37B7-4697-A35F-72598B0DC53B\\\”,\\\”correlationId\\\”:\\\”0B9BA0C0-3C13-4947-A8C3-AFCDDFEEC82B\\\”,\\\”tags\\\”:[],\\\”source\\\”:\\\”visitor\\\”,\\\”tag\\\”:{},\\\”score\\\”:0}]\”}]}” } }, “response”: { “status”: 200, “statusText”: “OK”, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Date”, “value”: “Sat, 09 Nov 2019 19:04:56 GMT” }, { “name”: “Content-Type”, “value”: “application/json; charset=utf-8” }, { “name”: “Transfer-Encoding”, “value”: “chunked” }, { “name”: “Access-Control-Allow-Credentials”, “value”: “true” }, { “name”: “Access-Control-Allow-Origin”, “value”: “; }, { “name”: “Access-Control-Max-Age”, “value”: “604800” }, { “name”: “P3p”, “value”: “CP=\”IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\”” }, { “name”: “X-Content-Type-Options”, “value”: “nosniff” } ], “cookies”: [], “content”: { “mimeType”: “application/json; charset=utf-8”, “size”: 63, “text”: “{\”items\”:[{\”statusCode\”:200,\”headers\”:{},\”contentHeaders\”:{}}]}” }, “redirectURL”: “”, “headersSize”: 361, “bodySize”: 424 }, “cache”: {}, “timings”: { “blocked”: 1, “dns”: 0, “connect”: 0, “ssl”: 0, “send”: 0, “wait”: 47, “receive”: 0 }, “time”: 48, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_3”, “startedDateTime”: “2019-11-09T20:04:57.371+01:00”, “request”: { “bodySize”: 5417, “method”: “POST”,

That wasn’t much. Some session ids but not much else.

From where is the call to google in being made and why ?

A javastip file


This login was very messy and the calls out to external parties most worrysome. But what strikes me as the most unusual is the call to Microsoft. And as a step in the login process too.
Why should Microsoft be involved in a federated login to a government agency ?


egrep -v “text\”:” HAR-file | tr -d “\n” | sed ‘s/”url”:/\n “url\”:/g’ | sed ‘s/ */ /g’ | egrep “^ \”url\”: \”https://%5B^/]*” | sed ‘s/”response”/\n\n\n”response”/g’ | sed ‘s/ “url”/\n\n\n”url”/g’

“url”: “;, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0” }, { “name”: “Accept”, “value”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “Referer”, “value”: “; }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Upgrade-Insecure-Requests”, “value”: “1” } ], “cookies”: [], “queryString”: [ { “name”: “p”, “value”: “b2c_1a_idporten” }, { “name”: “response_type”, “value”: “code” }, { “name”: “client_id”, “value”: “45104d6a-f5bc-4e8c-b352-4bbfc9381f25” }, { “name”: “redirect_uri”, “value”: “; }, { “name”: “scope”, “value”: “openid offline_access 45104d6a-f5bc-4e8c-b352-4bbfc9381f25” }, { “name”: “state”, “value”: “_JTWomMWhyZw6bZifoPHgUw-7OccK-hV9BKi1VaMQYk” }, { “name”: “nonce”, “value”: “HLPaFgxW368_-E6Rj5L6D0iT4yufUO6kydE21oT3QJE” }, { “name”: “level”, “value”: “Level3” } ], “headersSize”: 758 },

“response”: { “status”: 302, “statusText”: “Found”, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Cache-Control”, “value”: “private” }, { “name”: “Content-Type”, “value”: “text/html; charset=utf-8” }, { “name”: “Location”, “value”: “; }, { “name”: “x-ms-gateway-requestid”, “value”: “6be20d8a-8de5-4744-9554-0e5d916b0646” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e=aGxSZjI4UFlYMlhxZjN3dGs3bnpZc2Q3dCtSSmtkRDlJNVp6TWxwS3VSQ1VLZmE4UEVjdHBWSStmTWhaVWdHWHQvTU9BRHlBMXhrU2tmanpBaGNNWXc9PTsyMDE5LTExLTA5VDE5OjA0OjE5Ljc1MDE5MTRaO3ZOdVdHUGlMYTZLcVRrN2RJVWZFMmc9PTt7IlRhcmdldEVudGl0eSI6IklkUG9ydGVuRXhjaGFuZ2UiLCJPcmNoZXN0cmF0aW9uU3RlcCI6MX0=;; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0=m1.zcF9N87I+2/g1yeG.UTmH1gNjIyc69CLP0dMrJg==.0.xoG2yeldPtYiejoQ1QkTa6xU6Mp8IwRk4wbClfYYeaH7BczY8d++EydY4qYxTQSMLySLgHc+nROLyX1zwt4Zfb5gEN4TbAGP9uRMlrKDQE/SN6j5lU1UQA7nYqAIG+yq0t4BsO0KCh7Y9cK2oLi8S12xFGXwFHaB76r6DH4ZTaaJAROmz0cD/NAohTjqqi8YgrK0Lz03x64r0dbjHXTyXhgJJy91KBPGeTXc98WseRgsw429BSVfJToIC7rEfHk8ugnPZfZVIY7k13n1DGzpzKjfFZ67LsS6KEUg3YBDcs0AQULdPWZ15Bn0hk9vwfkBWMUi3kZRPxyfQyktCktF3wcbYQ5VYZh4f6BMz2RLqQHMPIDwzB5GSZvRuW7YkAywNb6dFS8yN4c9mx7EfgQjuexVmeJv+asyUASINQANZmpgHQQbDN/skHbOpSr4Zf+Sr/7wC0hy8vueOQFJad6hu6WYKp10b2MFlmLnnZyr9jeVdQEvTp0fsRu/6xacCH5fHG9VvjooZax89P8sBEyA/1HMGRURxGtaJpKHCllTlGaYrQYgn4ILcnANDHW8DWawHKM6sB8a2nS056/yyiPGVMaG+YV08qdWd/xM6J5I2e4KXzxKYdEIFYPVVg8otyp6M/mKK+QIxvHKKdcnGE93JcdYKhC0pOjznhQuxaaEZvXn8bit+WWzPeYxeSPWDQadXUNSXwG4L66AWn/X;; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-trans=eyJUX0RJQyI6W3siSSI6ImZhNjhlZGM3LWU4NDktNDMwNi04ZmZiLTkxMWJmMDI5M2Q2ZiIsIlQiOiJuYXZub2IyYy5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX2lkcG9ydGVuIiwiQyI6IjQ1MTA0ZDZhLWY1YmMtNGU4Yy1iMzUyLTRiYmZjOTM4MWYyNSIsIlMiOjEsIk0iOnt9LCJEIjowfV0sIkNfSUQiOiJmYTY4ZWRjNy1lODQ5LTQzMDYtOGZmYi05MTFiZjAyOTNkNmYifQ==;; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “X-Frame-Options”, “value”: “DENY” }, { “name”: “Strict-Transport-Security”, “value”: “max-age=31536000; includeSubDomains” }, { “name”: “X-Content-Type-Options”, “value”: “nosniff” }, { “name”: “X-XSS-Protection”, “value”: “1; mode=block” }, { “name”: “Set-Cookie”, “value”: “x-ms-gateway-slice=001-000; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “stsservicecookie=cpim_te; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Date”, “value”: “Sat, 09 Nov 2019 19:04:19 GMT” }, { “name”: “Content-Length”, “value”: “599” } ], “cookies”: [ { “name”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e”, “value”: “aGxSZjI4UFlYMlhxZjN3dGs3bnpZc2Q3dCtSSmtkRDlJNVp6TWxwS3VSQ1VLZmE4UEVjdHBWSStmTWhaVWdHWHQvTU9BRHlBMXhrU2tmanpBaGNNWXc9PTsyMDE5LTExLTA5VDE5OjA0OjE5Ljc1MDE5MTRaO3ZOdVdHUGlMYTZLcVRrN2RJVWZFMmc9PTt7IlRhcmdldEVudGl0eSI6IklkUG9ydGVuRXhjaGFuZ2UiLCJPcmNoZXN0cmF0aW9uU3RlcCI6MX0=” }, { “name”: “x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0”, “value”: “m1.zcF9N87I+2/g1yeG.UTmH1gNjIyc69CLP0dMrJg==.0.xoG2yeldPtYiejoQ1QkTa6xU6Mp8IwRk4wbClfYYeaH7BczY8d++EydY4qYxTQSMLySLgHc+nROLyX1zwt4Zfb5gEN4TbAGP9uRMlrKDQE/SN6j5lU1UQA7nYqAIG+yq0t4BsO0KCh7Y9cK2oLi8S12xFGXwFHaB76r6DH4ZTaaJAROmz0cD/NAohTjqqi8YgrK0Lz03x64r0dbjHXTyXhgJJy91KBPGeTXc98WseRgsw429BSVfJToIC7rEfHk8ugnPZfZVIY7k13n1DGzpzKjfFZ67LsS6KEUg3YBDcs0AQULdPWZ15Bn0hk9vwfkBWMUi3kZRPxyfQyktCktF3wcbYQ5VYZh4f6BMz2RLqQHMPIDwzB5GSZvRuW7YkAywNb6dFS8yN4c9mx7EfgQjuexVmeJv+asyUASINQANZmpgHQQbDN/skHbOpSr4Zf+Sr/7wC0hy8vueOQFJad6hu6WYKp10b2MFlmLnnZyr9jeVdQEvTp0fsRu/6xacCH5fHG9VvjooZax89P8sBEyA/1HMGRURxGtaJpKHCllTlGaYrQYgn4ILcnANDHW8DWawHKM6sB8a2nS056/yyiPGVMaG+YV08qdWd/xM6J5I2e4KXzxKYdEIFYPVVg8otyp6M/mKK+QIxvHKKdcnGE93JcdYKhC0pOjznhQuxaaEZvXn8bit+WWzPeYxeSPWDQadXUNSXwG4L66AWn/X” }, { “name”: “x-ms-cpim-trans”, “value”: “eyJUX0RJQyI6W3siSSI6ImZhNjhlZGM3LWU4NDktNDMwNi04ZmZiLTkxMWJmMDI5M2Q2ZiIsIlQiOiJuYXZub2IyYy5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX2lkcG9ydGVuIiwiQyI6IjQ1MTA0ZDZhLWY1YmMtNGU4Yy1iMzUyLTRiYmZjOTM4MWYyNSIsIlMiOjEsIk0iOnt9LCJEIjowfV0sIkNfSUQiOiJmYTY4ZWRjNy1lODQ5LTQzMDYtOGZmYi05MTFiZjAyOTNkNmYifQ==” }, { “name”: “x-ms-gateway-slice”, “value”: “001-000” }, { “name”: “stsservicecookie”, “value”: “cpim_te” } ], “content”: { “mimeType”: “text/html; charset=UTF-8”, “size”: 14892, “comment”: “Response bodies are not included.” }, “redirectURL”: “;, “headersSize”: 2634, “bodySize”: 17526 }, “cache”: {}, “timings”: { “blocked”: 202, “dns”: 0, “connect”: 79, “ssl”: 120, “send”: 0, “wait”: 110, “receive”: 0 }, “time”: 511, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_3”, “startedDateTime”: “2019-11-09T20:04:19.838+01:00”, “request”: { “bodySize”: 0, “method”: “GET”,


“url”: “;, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0” }, { “name”: “Accept”, “value”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “Content-Type”, “value”: “application/x-www-form-urlencoded” }, { “name”: “Content-Length”, “value”: “212” }, { “name”: “Origin”, “value”: “; }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Referer”, “value”: “; }, { “name”: “Cookie”, “value”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e=aGxSZjI4UFlYMlhxZjN3dGs3bnpZc2Q3dCtSSmtkRDlJNVp6TWxwS3VSQ1VLZmE4UEVjdHBWSStmTWhaVWdHWHQvTU9BRHlBMXhrU2tmanpBaGNNWXc9PTsyMDE5LTExLTA5VDE5OjA0OjE5Ljc1MDE5MTRaO3ZOdVdHUGlMYTZLcVRrN2RJVWZFMmc9PTt7IlRhcmdldEVudGl0eSI6IklkUG9ydGVuRXhjaGFuZ2UiLCJPcmNoZXN0cmF0aW9uU3RlcCI6MX0=; x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0=m1.zcF9N87I+2/g1yeG.UTmH1gNjIyc69CLP0dMrJg==.0.xoG2yeldPtYiejoQ1QkTa6xU6Mp8IwRk4wbClfYYeaH7BczY8d++EydY4qYxTQSMLySLgHc+nROLyX1zwt4Zfb5gEN4TbAGP9uRMlrKDQE/SN6j5lU1UQA7nYqAIG+yq0t4BsO0KCh7Y9cK2oLi8S12xFGXwFHaB76r6DH4ZTaaJAROmz0cD/NAohTjqqi8YgrK0Lz03x64r0dbjHXTyXhgJJy91KBPGeTXc98WseRgsw429BSVfJToIC7rEfHk8ugnPZfZVIY7k13n1DGzpzKjfFZ67LsS6KEUg3YBDcs0AQULdPWZ15Bn0hk9vwfkBWMUi3kZRPxyfQyktCktF3wcbYQ5VYZh4f6BMz2RLqQHMPIDwzB5GSZvRuW7YkAywNb6dFS8yN4c9mx7EfgQjuexVmeJv+asyUASINQANZmpgHQQbDN/skHbOpSr4Zf+Sr/7wC0hy8vueOQFJad6hu6WYKp10b2MFlmLnnZyr9jeVdQEvTp0fsRu/6xacCH5fHG9VvjooZax89P8sBEyA/1HMGRURxGtaJpKHCllTlGaYrQYgn4ILcnANDHW8DWawHKM6sB8a2nS056/yyiPGVMaG+YV08qdWd/xM6J5I2e4KXzxKYdEIFYPVVg8otyp6M/mKK+QIxvHKKdcnGE93JcdYKhC0pOjznhQuxaaEZvXn8bit+WWzPeYxeSPWDQadXUNSXwG4L66AWn/X; x-ms-cpim-trans=eyJUX0RJQyI6W3siSSI6ImZhNjhlZGM3LWU4NDktNDMwNi04ZmZiLTkxMWJmMDI5M2Q2ZiIsIlQiOiJuYXZub2IyYy5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX2lkcG9ydGVuIiwiQyI6IjQ1MTA0ZDZhLWY1YmMtNGU4Yy1iMzUyLTRiYmZjOTM4MWYyNSIsIlMiOjEsIk0iOnt9LCJEIjowfV0sIkNfSUQiOiJmYTY4ZWRjNy1lODQ5LTQzMDYtOGZmYi05MTFiZjAyOTNkNmYifQ==; x-ms-gateway-slice=001-000; stsservicecookie=cpim_te” }, { “name”: “Upgrade-Insecure-Requests”, “value”: “1” } ], “cookies”: [ { “name”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e”, “value”: “aGxSZjI4UFlYMlhxZjN3dGs3bnpZc2Q3dCtSSmtkRDlJNVp6TWxwS3VSQ1VLZmE4UEVjdHBWSStmTWhaVWdHWHQvTU9BRHlBMXhrU2tmanpBaGNNWXc9PTsyMDE5LTExLTA5VDE5OjA0OjE5Ljc1MDE5MTRaO3ZOdVdHUGlMYTZLcVRrN2RJVWZFMmc9PTt7IlRhcmdldEVudGl0eSI6IklkUG9ydGVuRXhjaGFuZ2UiLCJPcmNoZXN0cmF0aW9uU3RlcCI6MX0=” }, { “name”: “x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0”, “value”: “m1.zcF9N87I+2/g1yeG.UTmH1gNjIyc69CLP0dMrJg==.0.xoG2yeldPtYiejoQ1QkTa6xU6Mp8IwRk4wbClfYYeaH7BczY8d++EydY4qYxTQSMLySLgHc+nROLyX1zwt4Zfb5gEN4TbAGP9uRMlrKDQE/SN6j5lU1UQA7nYqAIG+yq0t4BsO0KCh7Y9cK2oLi8S12xFGXwFHaB76r6DH4ZTaaJAROmz0cD/NAohTjqqi8YgrK0Lz03x64r0dbjHXTyXhgJJy91KBPGeTXc98WseRgsw429BSVfJToIC7rEfHk8ugnPZfZVIY7k13n1DGzpzKjfFZ67LsS6KEUg3YBDcs0AQULdPWZ15Bn0hk9vwfkBWMUi3kZRPxyfQyktCktF3wcbYQ5VYZh4f6BMz2RLqQHMPIDwzB5GSZvRuW7YkAywNb6dFS8yN4c9mx7EfgQjuexVmeJv+asyUASINQANZmpgHQQbDN/skHbOpSr4Zf+Sr/7wC0hy8vueOQFJad6hu6WYKp10b2MFlmLnnZyr9jeVdQEvTp0fsRu/6xacCH5fHG9VvjooZax89P8sBEyA/1HMGRURxGtaJpKHCllTlGaYrQYgn4ILcnANDHW8DWawHKM6sB8a2nS056/yyiPGVMaG+YV08qdWd/xM6J5I2e4KXzxKYdEIFYPVVg8otyp6M/mKK+QIxvHKKdcnGE93JcdYKhC0pOjznhQuxaaEZvXn8bit+WWzPeYxeSPWDQadXUNSXwG4L66AWn/X” }, { “name”: “x-ms-cpim-trans”, “value”: “eyJUX0RJQyI6W3siSSI6ImZhNjhlZGM3LWU4NDktNDMwNi04ZmZiLTkxMWJmMDI5M2Q2ZiIsIlQiOiJuYXZub2IyYy5vbm1pY3Jvc29mdC5jb20iLCJQIjoiYjJjXzFhX2lkcG9ydGVuIiwiQyI6IjQ1MTA0ZDZhLWY1YmMtNGU4Yy1iMzUyLTRiYmZjOTM4MWYyNSIsIlMiOjEsIk0iOnt9LCJEIjowfV0sIkNfSUQiOiJmYTY4ZWRjNy1lODQ5LTQzMDYtOGZmYi05MTFiZjAyOTNkNmYifQ==” }, { “name”: “x-ms-gateway-slice”, “value”: “001-000” }, { “name”: “stsservicecookie”, “value”: “cpim_te” } ], “queryString”: [], “headersSize”: 2093, “postData”: { “mimeType”: “application/x-www-form-urlencoded”, “params”: [ { “name”: “code”, “value”: “MryzERHWMbxfjnvLYEiJpw7_ojLZ4qKowtDMHytb7I0” }, { “name”: “state”, “value”: “StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6MTQ4MTE4YzctODc3MC00MzQ2LTkwMGEtNTkwYWVkZmI5MThlIiwiVElEIjoiZmE2OGVkYzctZTg0OS00MzA2LThmZmItOTExYmYwMjkzZDZmIn0” } ], } },

“response”: { “status”: 302, “statusText”: “Found”, “httpVersion”: “HTTP/1.1”, “headers”: [ { “name”: “Cache-Control”, “value”: “private” }, { “name”: “Content-Type”, “value”: “text/html; charset=utf-8” }, { “name”: “Location”, “value”: “; }, { “name”: “x-ms-gateway-requestid”, “value”: “9c4f492d-2cb5-4b76-a4b1-f23caceeebf4” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e=;; expires=Fri, 08-Nov-2019 19:04:53 GMT; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-sso:navnob2c.onmicrosoft.com_0=m1.RP3BbsClW5XQVM+s.zoR66H3kXPuUX94F/tcPBw==.0.QGnjHX4eTe5vLHtrfAdtBkA0ZJK6aAaPM8CWVJSWvSLqR3JiWGuA0+9m9o2rYn2RUncXsHAlg3bAFD2rBSv69N96/niU3312pxLnuetVzCHHVneCRL0kw8OhYa5NQeHct+Vr4sC11K49f+9e5Cnvi8cVx/qntlWx0JWDqGpQhxFiSGJXBfk0sjr039GL7cvGCWezUbvjbQnz1cF6n8hCWqCk;; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0=;; expires=Fri, 08-Nov-2019 19:04:53 GMT; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “x-ms-cpim-trans=;; expires=Fri, 08-Nov-2019 19:04:53 GMT; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “X-Frame-Options”, “value”: “DENY” }, { “name”: “Strict-Transport-Security”, “value”: “max-age=31536000; includeSubDomains” }, { “name”: “X-Content-Type-Options”, “value”: “nosniff” }, { “name”: “X-XSS-Protection”, “value”: “1; mode=block” }, { “name”: “Set-Cookie”, “value”: “x-ms-gateway-slice=001-000; path=/; SameSite=None; secure; HttpOnly” }, { “name”: “Set-Cookie”, “value”: “stsservicecookie=cpim_te; path=/; secure; HttpOnly” }, { “name”: “Date”, “value”: “Sat, 09 Nov 2019 19:04:53 GMT” }, { “name”: “Content-Length”, “value”: “1359” } ], “cookies”: [ { “name”: “x-ms-cpim-rc:148118c7-8770-4346-900a-590aedfb918e”, “value”: “” }, { “name”: “x-ms-cpim-sso:navnob2c.onmicrosoft.com_0”, “value”: “m1.RP3BbsClW5XQVM+s.zoR66H3kXPuUX94F/tcPBw==.0.QGnjHX4eTe5vLHtrfAdtBkA0ZJK6aAaPM8CWVJSWvSLqR3JiWGuA0+9m9o2rYn2RUncXsHAlg3bAFD2rBSv69N96/niU3312pxLnuetVzCHHVneCRL0kw8OhYa5NQeHct+Vr4sC11K49f+9e5Cnvi8cVx/qntlWx0JWDqGpQhxFiSGJXBfk0sjr039GL7cvGCWezUbvjbQnz1cF6n8hCWqCk” }, { “name”: “x-ms-cpim-cache:x-1o-knobkop-5eb8ck9bw_0”, “value”: “” }, { “name”: “x-ms-cpim-trans”, “value”: “” }, { “name”: “x-ms-gateway-slice”, “value”: “001-000” }, { “name”: “stsservicecookie”, “value”: “cpim_te” } ], “content”: { “mimeType”: “text/html; charset=utf-8”, “size”: 44986, “comment”: “Response bodies are not included.” }, “redirectURL”: “;, “headersSize”: 2574, “bodySize”: 11337 }, “cache”: {}, “timings”: { “blocked”: 0, “dns”: 0, “connect”: 0, “ssl”: 0, “send”: 0, “wait”: 426, “receive”: 0 }, “time”: 426, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_3”, “startedDateTime”: “2019-11-09T20:04:53.489+01:00”, “request”: { “bodySize”: 0, “method”: “GET”,

There is plenty of guff here, but a picture emerges.

The first call;

which redirects to

On the way back
which redirects to

This is an Oauth transaction where Microsoft is the identity provider for NAV. The result is a JWT token passed from Microsoft to NAV. Microsoft in turn calls on Difi to establish the user’s identity.
At first look, Microsoft passes back more information than it receives. The call back from Difi to MS is a POST, but it contains only two parameters

MS to Difi

client_id: oidc_nav
response_type: code
scope: openid
response_mode: form_post
nonce: %2fC9sCl2kb7TZMD4tPS1%2fAg%3d%3d
acr_values: Level3
state: StateProperties%3deyJTSUQiOiJ4LW1zLWNwaW0tcmM6MTQ4MTE4YzctODc3MC00MzQ2LTkwMGEtNTkwYWVkZmI5MThlIiwiVElEIjoiZmE2OGVkYzctZTg0OS00MzA2LThmZmItOTExYmYwMjkzZDZmIn0

Difi to MS
code: MryzERHWMbxfjnvLYEiJpw7_ojLZ4qKowtDMHytb7I0
state: StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6MTQ4MTE4YzctODc3MC00MzQ2LTkwMGEtNTkwYWVkZmI5MThlIiwiVElEIjoiZmE2OGVkYzctZTg0OS00MzA2LThmZmItOTExYmYwMjkzZDZmIn0

state: _JTWomMWhyZw6bZifoPHgUw-7OccK-hV9BKi1VaMQYk&code=eyJraWQiOiJhT05QQk9fWDV0bzNIX2tsMllSTjRFRGdUMkVvQ201bmNCNlB1MEhOSlNJIiwidmVyIjoiMS4wIiwiemlwIjoiRGVmbGF0ZSIsInNlciI6IjEuMCJ9.b5lGSnVxsolX3Wa1gqGh5qqhm8upQh3laxBhYbqC2FixHYog53-6ilhTGxNevcqYL-gFih4xwTqNYJuEH7ux-eRr8YoUcv7Mv1TO3U-VtddA1O7ZMF6mbu3L8DcOFqHR7OahL4j_QVZm9z-gAYFl_yZvAMmQ4Selk_uKAzvwLkjE57u4S61nArLSknOJDV8XwpO4Ow_iicpL5RC_dr4jaLGH6WH6bMgLrtQ2uNfb2KhYQYQhkTmkYjqgx7fgPbqddz0OOCF8PUIEL7sKpl1-d0Uv75iAqXUJofZlFVDcDfBoO6noxaLVkWmNjWvHWmKHbHImmOTD__XtExAMoMK8wg.LJS-OSzaHhkhVERs.LCBP0FbMPn9OKkej4fmYlN9fj-cgwYPCKSulz96qY06wrmMOFokm5Iies7nsPsS2SD5WJGw-D4vlso-ac-yjHqNI_s-KequNz5XiNjMS_gaJYh19bfivmKJmCxOJjrobA95FzBAkJaOHezJNDWP_tlfB-0wzD8Y5JYCYJGw3CXvhlQDtNH3vAprgkEtA9sHxzoz_ejzZwT2Xrb5z2aI8RUJI1Y2WDHLKo8uXfkROJDAajYtWKIm2LcaFwdtnm90kXGHFB7tIRF76L8sOgc2IuK6l3UBlFpJcsaPeY-bvGK9rMotjSKqZjAiIs_OTkcpL_GPNbpyiicjQWdVFLLf51ivWlHvdADrCxDH20yLWu6GiYZMHUW4YXCDODhmrZx4LmEp35v_1Wpji-1HeKH3X7gPgZXVjhL9bK0ApnyCsUy6Vi-P5KyEy8Ne6UV0UKQZQjl58SoV67UKrabt44Wr9CDvYHCQwBh4WwJ-3pw2lF4-FbwXYE9A3ssdbAI5sM9M8XHAt537KRWTgaXw1xmrEQ3VQ8q6nNBIUhEKdtr43-q8NULpc2Yh8Q8fFPhBaUbtYSEeRKl-vrpiKI2yB4deG8bz6dwtgCiHdQ9R81hPCloAqNgUHzIl0roZkyu1acw.BDHTKPyICH1GZs9SIr-Xqg

Being bigger is not neccesarily important: the JSON in a JWT token contains metadata. And in this case the JWT token is also encrypted.
However, the stateproperties passed back and forth between MS and Difi is not

It contains only the base64 encoded JSON

If this is not encrypted, why is the JWT encrypted ? Either both or neither to my mind. The question is not confidentiality here, these are merely temprary reference values, but rather integrity. In this case I’d say both should have been encrypted. It seems the connection between MS and Difi is not robust.
If MS maintains state such that the authorization token and stateproperties received from Difi is matched with the statepropertes sent to Difi – then fine. If not, then not fine at all.

What is in the JWT ? It is too big for only metadata. And nothing about the user was passed from Difi to MS, so if the JWT contain user info, where did it come from?

But this begs a larger question. Why is involved at all ?
Because it is not necessary, obviously. Another example from another government service. They set up a much tighter login, still using the same federation partner Bankid.

Similar domain name hit histogram as above


Clearly a very much tighter operation here. Since the government federation broker is here to the minimum number of domains, including the ID-provider, is 3. A first look a total of four domains involved must be considered a solid piece of work. Everything has a dot-no domain. So GDPR should not be a concern.
But perhaps the details show some things slipping through where it shouldn’t.

“url”: “;, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0” }, { “name”: “Accept”, “value”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Referer”, “value”: “; }, { “name”: “Cookie”, “value”: “XSRF-TOKEN=a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c; ApplicationGatewayAffinity=218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0” }, { “name”: “Upgrade-Insecure-Requests”, “value”: “1” }, { “name”: “TE”, “value”: “Trailers” } ], “cookies”: [ { “name”: “XSRF-TOKEN”, “value”: “a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c” }, { “name”: “ApplicationGatewayAffinity”, “value”: “218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0” } ], “queryString”: [ { “name”: “type”, “value”: “sykkel” } ], “headersSize”: 627 }, “response”: { “status”: 302, “statusText”: “Found”, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “cache-control”, “value”: “no-cache, no-store, max-age=0, must-revalidate” }, { “name”: “pragma”, “value”: “no-cache” }, { “name”: “expires”, “value”: “0” }, { “name”: “location”, “value”: “; }, { “name”: “set-cookie”, “value”: “JSESSIONID=53848E5BB6B11F125EB7524836844FEB; Path=/; Secure; HttpOnly” }, { “name”: “x-content-type-options”, “value”: “nosniff” }, { “name”: “x-xss-protection”, “value”: “1; mode=block” }, { “name”: “strict-transport-security”, “value”: “max-age=1036800 ; includeSubDomains” }, { “name”: “x-frame-options”, “value”: “DENY” }, { “name”: “content-security-policy”, “value”: “default-src ‘self’;connect-src ‘self’ https://*;style-src ‘self’ ‘unsafe-inline’;img-src ‘self’ data:” }, { “name”: “date”, “value”: “Sun, 05 Jan 2020 10:33:29 GMT” }, { “name”: “content-length”, “value”: “0” }, { “name”: “X-Firefox-Spdy”, “value”: “h2” } ], “cookies”: [ { “name”: “JSESSIONID”, “value”: “53848E5BB6B11F125EB7524836844FEB” } ], “content”: { “mimeType”: “text/html; charset=UTF-8”, “size”: 15043, “comment”: “Response bodies are not included.” }, “redirectURL”: “;, “headersSize”: 639, “bodySize”: 15682 }, “cache”: {}, “timings”: { “blocked”: 0, “dns”: 0, “connect”: 0, “ssl”: 0, “send”: 0, “wait”: 69, “receive”: 0 }, “time”: 69, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_1”, “startedDateTime”: “2020-01-05T11:33:28.807+01:00”, “request”: { “bodySize”: 0, “method”: “GET”,

“url”: “;, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0” }, { “name”: “Accept”, “value”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “Referer”, “value”: “; }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Cookie”, “value”: “XSRF-TOKEN=a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c; ApplicationGatewayAffinity=218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0; JSESSIONID=53848E5BB6B11F125EB7524836844FEB” }, { “name”: “Upgrade-Insecure-Requests”, “value”: “1” }, { “name”: “TE”, “value”: “Trailers” } ], “cookies”: [ { “name”: “XSRF-TOKEN”, “value”: “a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c” }, { “name”: “ApplicationGatewayAffinity”, “value”: “218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0” }, { “name”: “JSESSIONID”, “value”: “53848E5BB6B11F125EB7524836844FEB” } ], “queryString”: [], “headersSize”: 659 }, “response”: { “status”: 302, “statusText”: “Found”, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “cache-control”, “value”: “no-cache, no-store, max-age=0, must-revalidate” }, { “name”: “pragma”, “value”: “no-cache” }, { “name”: “expires”, “value”: “0” }, { “name”: “location”, “value”: “; }, { “name”: “x-content-type-options”, “value”: “nosniff” }, { “name”: “x-xss-protection”, “value”: “1; mode=block” }, { “name”: “strict-transport-security”, “value”: “max-age=1036800 ; includeSubDomains” }, { “name”: “x-frame-options”, “value”: “DENY” }, { “name”: “content-security-policy”, “value”: “default-src ‘self’;connect-src ‘self’ https://*;style-src ‘self’ ‘unsafe-inline’;img-src ‘self’ data:” }, { “name”: “date”, “value”: “Sun, 05 Jan 2020 10:33:29 GMT” }, { “name”: “content-length”, “value”: “0” }, { “name”: “X-Firefox-Spdy”, “value”: “h2” } ], “cookies”: [], “content”: { “mimeType”: “text/html; charset=UTF-8”, “size”: 15043, “comment”: “Response bodies are not included.” }, “redirectURL”: “;, “headersSize”: 812, “bodySize”: 15855 }, “cache”: {}, “timings”: { “blocked”: 0, “dns”: 2, “connect”: 0, “ssl”: 0, “send”: 0, “wait”: 53, “receive”: 0 }, “time”: 55, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_1”, “startedDateTime”: “2020-01-05T11:33:28.860+01:00”, “request”: { “bodySize”: 0, “method”: “GET”,

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;

“Location”: “

“url”: “;, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “Host”, “value”: “” }, { “name”: “User-Agent”, “value”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0” }, { “name”: “Accept”, “value”: “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8” }, { “name”: “Accept-Language”, “value”: “en-GB,en;q=0.5” }, { “name”: “Accept-Encoding”, “value”: “gzip, deflate, br” }, { “name”: “Referer”, “value”: “; }, { “name”: “DNT”, “value”: “1” }, { “name”: “Connection”, “value”: “keep-alive” }, { “name”: “Cookie”, “value”: “XSRF-TOKEN=a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c; ApplicationGatewayAffinity=218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0; JSESSIONID=53848E5BB6B11F125EB7524836844FEB” }, { “name”: “Upgrade-Insecure-Requests”, “value”: “1” }, { “name”: “TE”, “value”: “Trailers” } ], “cookies”: [ { “name”: “XSRF-TOKEN”, “value”: “a308a8d1-ffa5-4591-9c1e-6d2e0af98e8c” }, { “name”: “ApplicationGatewayAffinity”, “value”: “218f983d4932882bd40a98762d95d965a6cdbef861a68362433bb959d0589ee0” }, { “name”: “JSESSIONID”, “value”: “53848E5BB6B11F125EB7524836844FEB” } ], “queryString”: [ { “name”: “code”, “value”: “gtJFV1p5nKatkV7FyXkU02jhcfPFgEoz47k4q7PnCxY” }, { “name”: “state”, “value”: “kzSBcqvpGyGRUMEQ8sejiIJYcQxzTslzRfW4lG1_t7U=” } ], “headersSize”: 707 }, “response”: { “status”: 302, “statusText”: “Found”, “httpVersion”: “HTTP/2.0”, “headers”: [ { “name”: “cache-control”, “value”: “no-cache, no-store, max-age=0, must-revalidate” }, { “name”: “pragma”, “value”: “no-cache” }, { “name”: “expires”, “value”: “0” }, { “name”: “location”, “value”: “; }, { “name”: “set-cookie”, “value”: “JSESSIONID=93798A700538A6625D75042F2816EB6B; Path=/; Secure; HttpOnly” }, { “name”: “set-cookie”, “value”: “XSRF-TOKEN=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; Secure” }, { “name”: “set-cookie”, “value”: “XSRF-TOKEN=76c82040-160f-471c-a4e0-4d85e9c6b361; Path=/; Secure” }, { “name”: “x-content-type-options”, “value”: “nosniff” }, { “name”: “x-xss-protection”, “value”: “1; mode=block” }, { “name”: “strict-transport-security”, “value”: “max-age=1036800 ; includeSubDomains” }, { “name”: “x-frame-options”, “value”: “DENY” }, { “name”: “content-security-policy”, “value”: “default-src ‘self’;connect-src ‘self’ https://*;style-src ‘self’ ‘unsafe-inline’;img-src ‘self’ data:” }, { “name”: “date”, “value”: “Sun, 05 Jan 2020 10:34:20 GMT” }, { “name”: “content-length”, “value”: “0” }, { “name”: “X-Firefox-Spdy”, “value”: “h2” } ], “cookies”: [ { “name”: “JSESSIONID”, “value”: “93798A700538A6625D75042F2816EB6B” }, { “name”: “XSRF-TOKEN”, “value”: “” }, { “name”: “XSRF-TOKEN”, “value”: “76c82040-160f-471c-a4e0-4d85e9c6b361” } ], “content”: { “mimeType”: “text/html”, “size”: 724, “comment”: “Response bodies are not included.” }, “redirectURL”: “;, “headersSize”: 794, “bodySize”: 1362 }, “cache”: {}, “timings”: { “blocked”: 0, “dns”: 1, “connect”: 0, “ssl”: 0, “send”: 0, “wait”: 284, “receive”: 0 }, “time”: 285, “_securityState”: “secure”, “serverIPAddress”: “”, “connection”: “443” }, { “pageref”: “page_1”, “startedDateTime”: “2020-01-05T11:34:19.748+01:00”, “request”: { “bodySize”: 0, “method”: “GET”,

Both Oauth and SAML are involed, but there is nothing wrong with that. For my money SAML is more robust (let’s hear it for signed and encrypted tokens contaning usefull data!), but OAuth is of course newer and leaner if you think byte count in HTTP traffic matters.

Token based API access control has a critical flaw

I’ve written on this subject before. I come back to the subject because I keep coming across this problem. Granted where or not it is a problem depends on how you look at things. My view is from the point of view of robustness and fail-safety.

I work with programmers quite a bit and theirs is to deliver functionality out the door. Which is understandable: that is what they are measured on.

Then I come along later and try to track down a problem. Over privileged service accounts which is what these API access tokens amount to is a perennial problem. Leaving aside for the moment the security aspect of it. A service account and an API access token can be poorly implemented in two main ways. Be given too much access privileges, let’s call that a Type 1 fault; and be shared by too many different entities, call that Type 2. I’ve seen plenty of both. Okta is a good(bad) example of the former. Their SaaS REST API only grants one kind of access token. To everything.  Type 1 fault as bad as can be.

Programmers using the API for Okta can then keep reusing the same access token – it is just a text string after all – to any extent rather than going to the rigmarole of issuing another. Yes, it happens. A lot. Type 2 fault. The API access tokens are usually valid for a long time since they are meant to be used by other applications, hence the service account equivalence, but can be selectively revoked and at least in Okta there is good overview of currently valid tokens. But not what has been going on with them and what they have been used for.

In Unix/Linux these API tokens are like having multiple root accounts that have been shared with people you do not know and being used for unknown things. No one would call that a satisfactory state of affairs on a *x server system. But it is rarely expressed in those terms.

The root account comparison is apt. On unix -like systems the root user is not subject to OS-level access control. Access is unlimited. Switching to any other user however souped-up, is better. But it involves actually implementing some access control: groups and permissions in a unix-like system. Which can be laborious – and worse, error prone. So let’s just give it root – at least it will work.

Yes, laziness. The malicious user/intruders best friend.  API access tokens are employed on great many platforms but since we call them API access tokens rather than user/service accounts, little or no scrutiny is ever applied to them.

Now we have these API tokens with good-like system powers. Revocable, sure, but does that ever happen ?

Funny symptoms on OpenAM when session handling is under strain.

Of late I have been examining  an intermittent error situation at a large OpenAM implementation. Several aspects of the implementation are not in accordance with the recommendation of the vendor, or even good practice. But that has afforded some very good learning opportunities. As is often the case when straying from the preferred path.

The deployment there are four OpenAM instances and four peer-to-peer replicated OpenDJ instances for data storage.

OpenAM keeps policy, configuration and session information in a directory. OpenDJ in this scenario. It is for obvious reasons recommended to keep the session store (it takes more load and does not need a backup) separate from the policy and configuration store.

An early clue was that the error conditions where resolvable with restarts. Clearly this was not fundamentally a policy matter. Policies persist across restarts. Sessions however, do not. Following up on this it was discovered that the sessions store and policy store where collocated.

OpenAM has caching on session token validation operations. So there would be no need to ask the directory everytime a session token is to be validated. But this is an object cache, not a search cache. So the session object can be cached on the OpenAM instance but if there is an attempt to validate a non-existent token, that attempt would result in a search against the underlying session object store. Every time. Examination of the search logs on the object store revealed that between 12% and 40% of token search operations were for tokens that did not exist.

It gets worse. Since the customer had declined to implement stickyness on the load balancer in front of the multiple OpenAM instances, users were sprayed across all of them. This leads to an excessive number of session token modification operations. Quite a large number of them in fact; Many tokens were modified hundreds of times. And with all modifications replicated across all of the directory store instances, the load on the OpenDJ instances is considerable.

But wait, it gets worse still. Forgerock is quite adamant that there can be no load balancing going on between the OpenAM and directories used as session store. In this design there are four OpenDJ OpenDJ instances used for sessions store, one designated for each OpenAM instance. Should one OpenDJ instance become unavailable “it’s” OpenAM instance will use one of the others, but otherwise the OpenAM instance will used it’s designated OpenDJ instance for all session token operations.

One can of course argue about what constitutes load balancing but I think of it as being chiefly about load distribution, with or with out stickyness. This design I would describe the OpenAM to OpenDJ connection to load balanced with permanent stickyness.

Does this matter ? In a replicated environment all the OpenDJ instances will be equal. Ideally, no. But not always. There will always be some replication delay. The problem here was the users being distributed across all OpenAM instances. Meaning that the same token operation could arrive at two, or more, different OpenAM instances practically simultaneously. Replication is fast in a good network and with good indexing. But it does not take zero time. If different OpenAM instances uses different OpenDJ instances as session token store, there will be errors. Probably not many, but ones likely to increase in frequency with increasing system load.

Ok. How to determine this. First: have plenty of logging on the OpenDJs. The search request for session tokens are easy to identity in the OpenDJ access log. They are base-level searches and therefore simple to extract reliably with an egrep statement. The same with failures to find a token. In fact directly related to the base level searches for tokens. A base level search when the base object does not exist produces an error to that effect. In this case the access log clearly states that the session token object is not present. Very convenient. And egrep-able.

So it is easy the get a list of tokens being searched for and which ones were not found. Extract these list from all OpenDJ instances within the same time period – say one hour. And determine if there where any failed token searches on any instance that succeeded on any of the others. There were.


We all gripe about vendor documentation. And Forerock has had some doosies. But there is usually good advice in there. Don’t ignore it without very good reason backed up by observational data.

The short and sweet on OpenAM, play nice with your session handling.

true desktop Single Sign-on for any web based application ?… get out of here.

Single SIgn-on, SSO for short, is an an attractive but elusive thing. Log into to your dekstop PC and be signed in to all your other applications – be they local or remote.  Your cloud provided business applications: Office 365, Amazon Web Services. Your online news site: The Economist etc.  Without having to enter username and password again. This does sound attractive.

So much so that some have found it expedient to sell something as SSO which strictly speaking is only single-login. There are tools for propagating usernames and synchronizing users stores. But they don’t log you in when you need to. You still have to enter your username and password. This is not SSO, merely single provisioning. Helpful, but still a nuisance with repeated entering of login information. What we want is true Single Sign-on.

All those login names and passwords. If you consistently use only your email address (and always the same one) as user name. Perhaps even, incautiously use the same password everywhere. There is sheer nuisance of it all.  Which in and of it self is enough to make people use the same password everywhere. This makes corporate systems vulnerable. If someone has used a password in an external system and this system is compromised, and the password revealed. Well, the attacker now has a very good password first guess for further attacks. So SSO which obviates the practical need to unified login information, but particularly the re-use of passwords, is much needed.

Such solutions do exists. User federation happen in many ways. You can now sing in to many applications simply by using your login to Facebook. Microsoft provides ADFS which allows SSO to many applications in the Windows sphere, most particularly Office 365, from you Windows desktop. This is what we want SSO from the desktop, but without the limitations of ADFS. We want to be able to login anywhere, not just to places that have been configured for ADFS.

For the purposes of this article I’ll limit myself to Sing-on part – the actual loging in. This presupposes the existence of an account in the target system, leaving just-in-time-provisioning aside (but see here for exploration on the topic of account-less access) which I will assume is already in place. There are any number of ways to provision accounts. and to the extent that this is a one-time thing, not really much of a bother for the end user.

Recently I had occasion to explore the identity management tools from Okta ( which promised to deliver on this tall order.


What follows is not a complete “cook book.” but it will include all the steps I went through , in order, to put in place true desktop SSO using the Okta framework. Both for an application that accepts SAML based federated login: Here I picked Amazon Web Services, AWS, for the demonstration. And one that just has a username/password login: Here I selected The Economist.



The Okta SSO architecture contain the following components:

The user’s Windows desktop PC.

The Windows domain controller, DC, to which it subscribes,

The Okta server at https://<your domain>, When your organization subscribed to Okta, this is set up for you with a sub-domain name of  your choosing. There is also an administration domain name of the form <yourdomain>-admin,,

The user’s browser,The SSO is only for web based applications. A browser is always employed in their access.

The plugin provided by Okta for that browser. The supported browsers are Internet Explorer, Mozilla Firefox, Google Chrome and Safari. This plugin is essential for populating login fields for non-SAML based logins.

Okta agent for the Windows DC. This agent must be installed somewhere in you corporate infrastructure on a Windows server with direct access to the DC. In this case it was installed directly on the DC.

Okta’s Integrated Windows Authentication, IWA, login application. This is a web applications that runs on an IIS instance in your corporate Windows server infrastructure. When your browser is configured to send Windows Authentication information to configured list of web domains, this application is on that list. This is the main way the ID with which you are authenticated to your desktop is collected by the Okta infrastructure. In this case it was installed directly on the DC.


First subscribe to Okta and login to the Okta admin interface. Okta has two free subscription options: A developers license with a limit on the number of different applications that can be connected to it, but last forever. Or a regular, “enterprise”, license with no limits on the applications but which is good (read: free) for only a 30-day trial period. In this example I have selected the “regular” one since the time limit is of no concern here.

After signup, an confirmation email is sent to you with the relevant login details.

Login to your corporate Okta page as the admin and go on to the administration interface (the “admin” button) From Setting- Download download the following:

The plugin for the browser(s) you are using. Keep the plugin install files for later installation in other browsers.

The AD agent installer.

SSO IWA Web App Installer

We won’t use the password synch agent in this exercise.


Create a serviceaccount for the AD Agent on the DC. I called it “OktaService” and placed in the root container in my “cloudworks” (dc=cloudworks,dc=demo) domain. This user must be granted permissions to log on as a service and as a batchjob. Plus the, for service accounts usual do not change at first logon and password never expires options.

Run the AD Agent installer (OktaADAgentSetup-3.2.1.exe) on a windows Server with access to the DC. I used Windows 2012 R2 which also hosts the DC, and this proved an excellent choice.

The installer ask for

Install path: accept defult

Domain: cloudwork.demo

Okta AD Agent service account: this will be used to access AD and is the one created earlier: “OktaService@cloudworks.demo” / password

Proxy Configuration: no

Register Okta AD Agent: The agent will be connected to the Okta infrastructure. specify you domain here. https.//  which means just ticking “Production” and entering “cloudwork” in the subdomain field.

A browser window opens and a HTTP connection is being made to Okta.  You are presnted wit ha login window where you specify the administrator login details for your corporate Okta account established above.

A question is presented: Okta AD Agent is requesting permission to access the Okta API. Click “Allow Access”.

Install the IWA application. This is a web application that we will install on an instance of IIS (version 8.5.9600 running locally on the DC. But you can install the IWA application on any existing instance of IIS you have handy.

The IWA installer request the details of the service account the IWA applicaiton will need to run (specifically the application pool set up in the IIS) I reused OktaService account from above.

Specify the okta subdomain to be used: “cloudworks”, in this example. Once again a browser windows is openen and a login windows to Okta is presented. Enter the administration login as per above.  “Okta Desktop SSO Agent is requesting permission to: access the entire Okta API” ? Click “Allow Access”.

Go to the IIS manager where the IWA is installed and make sure it really is running. It should have an Application pool names OktaIWA with the identity specified with the installer. One of the installed Sites should be IWA. Under it in the web tree you should see the directories: bin, certificate, conftemplate, docs, img, installerlog and tools.

Log in to the Okta admin console at https://<your subdomain> which is

This login works on Internet Explorer but not on Firefox. initial front page









The message states that I do not have any applications. Which makes sense since I have not configured any yet. I logged in as the administrator so the little triangle next to my name is a drop-down menu with “Admin” on it. Selecting this brings me to the administration interface for our Okta implementation. This URL: which I will use directly from now on.

admin initial front page











Token-in-token, Agent-Principal model of authorization delegation

PAML tokens create portable authorization where the Enforcement point need not know either the Principal making the request or the Owner authorized to grant the request. But the Principal/user must be in direct contact with the enforcement point to establish, through a cryptographic handshake, ownership of the PAML token being used. In many scenarios this direct connection is undesirable, even impossible. The Principal can not have a direct connection to the enforcement point.
A medical records repository is a good example. Security rules prohibit direct access over the internet to such a repository. Intermediary entities, application servers, firewall and the like, are mandated gateways.
In effect someone or something, must act on behalf of the Principal with the Enforcement point: An Agent.

This can be effected by placing one PAML token inside another. This token-in-token functionality is described in detail below. Any number of tokens can be embedded inside one another. In some ways like a certificate chain.
As with a standard Agent-Principal model the Principal trusts the Agent to act in the way the Principal desires. But with PAML token the Principal can narrow the Agents latitude to the extent desired.

Definition of terms

The Principal. This is the person, or entity, on whose behalf the request is undertaken.

Agent. The person or entity carrying out the request task on behalf of the Principal.

Owner. The person or entity that decides if the Principal is entitled to have the request(s) undertaken.

Enforcer. Person or entity in control of the resource and handles the request from the Agent. The Enforcer accepts the PAML token for authorization decisions.

Here is a schematic of the flow

alt text

Agent-Principal use of PAML tokens


Explanation of steps
Step 1 Principal prepares a list of request, or logical super-set of requests, in an XML format document; and signs this list with the Principal’s own private key. The logical super-sets can include rules according to which the requests made by the agent should be evaluated. The Agent can only carry out request on this list or ones contained in super-sets on this list. The XML document can include XSLT operations to be applied at enforcement time to the request made by the Agent. With this XSLT the Principal can enforce additional limitations (such as timing or checking token revocation list) on the requests the Agent is making.

Creating (pseudo XML document)


Using the private key used to sign the request list document and the public key that belong to it: engage in cryptographic handshake with Agent and through this, establish to the Agent’s satisfaction that the Principal is in possession of the private key used to sign the request list.
Pass the signed list to the Agent.

Step 2 Agent attaches own usage conditions to Principal’s signed request.



And sign with the Agent’s own private key.


Using the private key used to sign the document and the public key that belong to it: engage in cryptographic handshake with the Owner and through this, establish to the Owner’s satisfaction that the Agent is in possession of the private key used to sign the document.
Pass the signed document to the Owner.

Step 3
Owner attaches own usage conditions to Agent’s signed request.



And sign with the Owners own private key. This is the same private key used to sign the resource the PAML token governs access to.



The PAML token is now completed.
Return the PAML to the Agent.

Step 4
The Agent now has a PAML token providing authorized access to resources signed with the same private key that signed the token. The PAML token can be reused to the limits specified in the owner’s usage conditions. Typically these would at least include validity time periods.
The Agent can now make request to the Enforcer and have them be authorized by the token. The requests are on behalf of the Principal but do not have to be directly from the Principal.

The Agent engages in a cryptographic handshake with the Enforcer using the private key used to sign the document from the Principal and the public key that belong to it: engage in cryptographic handshake with the Enforcer and through this, establish to the Enforcer’s satisfaction that the Agent is in possession of the private key used to create agent signature in the PAML token document.
Passed one or more PAML tokens to the Enforcer.
Pass a request to the Enforcer.

Step 5

The Enforcer examines the PAML tokens it has received. The tokens are checked in the following way and order:
XML validation. Those containing invalid XML are discarded.
XML schema validation. Those containing improper XML are discarded.
Agents signature validation: Using the public key of the Agent established in the cryptographic handshake the agent signature in the token validated. Tokens failing this validation are discarded.
Remaining tokens are tokens the Agent is properly entitled to use.

Examine the request and match it to the request list part of the remaining PAML tokens.
Those token that do not govern the request are discarded. Meaning that the request list in the token(s) do(es) not have at least one match for the request.
The remaining PAML tokens, if any, both belong to the Agent and govern the request the Agent is making.
The Enforcer examines the resource the request is for and what, if any digital signature it bears. Only if the resource bears a signature that can be validated with the same public key that can also validate the owners signature on the PAML token, is the resource considered “in scope” for the request. These two signature validation are carried out using the public key of the owner included with owner’s signature on the PAML token. If both signatures can be successfully validated the request is authorized.

In step 5 we get the desired situation that a completely stand-alone enforcement point can make authorization decisions for agents acting on behalf of someone else, without having to know the identity of either of the parties. This gives obvious benefits in reduced complexity and  ease of deployment.