Authentication using OAuth2 Implicit Flow using Azure Active Directory

piotr szybicki
12 developer labors
5 min readApr 15, 2019

--

I recently begun to work in a new company. And unlike my previous job the goto cloud provider is the Microsoft Azure. We have plans to develop Angular application that spark my question how to secure it. So I set out to do it. Given that I have pretty good understanding of the OAuth2 and OpenIDConnect Authorization Code Flow I more or less knew what I want to achieve. Turn out is was simpler then I originally suspected. As Microsoft does not have a history of making my life easier. If you want to fallow along with me pleas prepare the set up in AD first. The azure docs do pretty good job so I will not bother:

Pleas follow only the steps done from the azure console I will help with the rest. One thing when they ask you to provide redirect URI just type: http://localhost:4200

Authorization Code Flow

First let me tell you right of the bat that Microsoft implements OAuth2 spec in a confusing way as it can be used both for delegated authorization (something it was designed for) and delegated authentication (something that original spec was missing). Plus for both the same endpoint is used, which doesn’t help. In my opinion this is a historical mistake as there was a point in time when the OAuth2 was adopted as a guarantor of user identity. However every company (Google, Microsoft, LinkedIn, Twitter) did it slightly different way. And that is why additional 5% on top of OAuth was added and called OpenIDConnect. Mainly around retrieving user information.

The title of this peace read ‘Authentication …’ so that would suggest OpenIDConnect flow will be presented however Microsoft has a TypeScript library that uses the OAuth2 Implicit Flow for authorization. And I struggle with a though for a while to go against the current and write my own but I decided to be lazy and not reinvent the wheel.

OK. So now a little bit of theory. When I say implicit flow (type of the OAuth2 flow there are 3 more) what I actually mean is a bunch of http request exchange between browser and identity provider (in this case Azure AD).

So let’s analyze thous requests, first we go to http://localhost:4200 where js code checks if the user is logged in, it is not the it’s redirected to following url:

GET https://login.microsoftonline.com/689c417e-2596-4b1e-ad56-976712af76a1/oauth2/authorize?
client_id=52342c78-c557-48ef-8f09-be40c2093edf
&response_type=id_token
&redirect_uri=http%3A%2F%2Flocalhost%3A4200
&state=b597dbdf-7c80-4aa2-bb4e-6e76a100cffe
&client-request-id=9905b985-ae8a-4e0b-9c41-b40a4d301672
&x-client-SKU=Js&x-client-Ver=1.0.17
&nonce=7362CAEA-9CA5–4B43–9BA3–34D7C303EBA7

let’s analyze what is passed first thing that is boded is the tenatId. This is an unique id for your active directory. Client_id is the id of the registered app. respons_type is the required parameter and it has to be id_token, token, code. redirect_uri where you should be redirected to after successful login. Nonce is a random value design to prevent replay attacks.

And this is where things got a little bit confusing for me as couple of things ware missing: scope (spec says that it have to be comma separated values and the first have to be openid) and response_mode (methods that specified the resulting authentication code back) but it turns out that in their absence the azure AD has reasonable defaults.

OK so now after this long explanation user is on the login page enters credentials and after successful login the 302 Moved Temporarily redirect is issued to the following address:

http://localhost:4200/#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsImtpZCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiI1MjM0MmM3OC1jNTU3LTQ4ZWYtOGYwOS1iZTQwYzIwOTNlZGYiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82ODljNDE3ZS0yNTk2LTRiMWUtYWQ1Ni05NzY3MTJhZjc2YTEvIiwiaWF0IjoxNTU1MjM1Nzc5LCJuYmYiOjE1NTUyMzU3NzksImV4cCI6MTU1NTIzOTY3OSwiYWlvIjoiNDJaZ1lEaGk3SEM3amp2NnpaR2FlTGJhQzlyZU5YWm1BdFcycnpZZlVlbSt5bE51UEFjQSIsImFtciI6WyJwd2QiXSwiaXBhZGRyIjoiMTQ1LjYyLjY0LjEwMCIsIm5hbWUiOiJwaW90cmVrIiwibm9uY2UiOiJkYWZhNWY4My01YWRkLTQ5OWQtYTA1Ny02YzMyNmQ2ZDNjNzUiLCJvaWQiOiI2YzkyNzI1ZS03NTk5LTQ0YmUtOTJmOC1kOTc2MGRjNzM5NTIiLCJzdWIiOiJNQWRyUXFXUTNGaV92RjhGbWFqaUZCRzhjQWs4Snp1OWVlTl9DbFVRakpnIiwidGlkIjoiNjg5YzQxN2UtMjU5Ni00YjFlLWFkNTYtOTc2NzEyYWY3NmExIiwidW5pcXVlX25hbWUiOiJ1c2VyQHBzenliaWtja2lhZC5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJ1c2VyQHBzenliaWtja2lhZC5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJLcTNQWGdycXAwdVp0dFNCZnVFWkFBIiwidmVyIjoiMS4wIn0.XtKvYyeZUfxKBVn_c4TYyrFYLmDGgscUknW33rhPd8q5b_VepzwgEZZBVmQ-IraB47U_EzO7dSoDTS8vP8mxxR7EJQBNkYNEDQVb2NykHKD7RrIV3oLaSZPzS4r2hTvvP7eWDtrZGAarmOObTqVlyNKXsAr_oduET3gpbPctaCdOCrRIZb7ssmILD8gZp_gy9ZrLTZEb54v3Bi_rJMz45S9jFKaw_obGWu9vq0tddFY4_DXPMPns_BWgHmzMEhUB6WNrDQw7OykiR-CB_0MWD1kbsDAX6vOzro8UDTF9SGs7-vzlhFRKuJSZwJPDyRbr6e3KYcxyfHhmOrkbZh49Cw&state=b597dbdf-7c80-4aa2-bb4e-6e76a100cffe&session_state=70bffd08-5390-444b-9b9d-6cbb3bf5e554

The Id token is whet we requested earlier. It’s something called JWT (JSON Web Token) and you can copy/paste it (https://jwt.io/) to decode it into something more readable:

{
"aud": "52342c78-c557-48ef-8f09-be40c2093edf",
"iss": "https://sts.windows.net/689c417e-2596-4b1e-ad56-976712af76a1/",
"iat": 1555235779,
"nbf": 1555235779,
"exp": 1555239679,
"aio": "42ZgYDhi7HC7jjv6zZGaeLbaC9reNXZmAtW2rzYfUem+ylNuPAcA",
"amr": [
"pwd"
],
"ipaddr": "145.62.64.100",
"name": "piotrek",
"nonce": "dafa5f83-5add-499d-a057-6c326d6d3c75",
"oid": "6c92725e-7599-44be-92f8-d9760dc73952",
"sub": "MAdrQqWQ3Fi_vF8FmajiFBG8cAk8Jzu9eeN_ClUQjJg",
"tid": "689c417e-2596-4b1e-ad56-976712af76a1",
"unique_name": "user@pszybikckiad.onmicrosoft.com",
"upn": "user@pszybikckiad.onmicrosoft.com",
"uti": "Kq3PXgrqp0uZttSBfuEZAA",
"ver": "1.0"
}

This is just the middle of three parts that comprise the JWT. The last one is a cryptographic signature that allows us to verify that the token has not been tampered with. You can do it yourself go to the https://jwt.io/ paste the full token to decode it the go to:

https://login.microsoftonline.com/{tanant_id}/.well-known/openid-configuration

there you will have the link to retrieve the public key

"jwks_uri": "https://login.microsoftonline.com/common/discovery/keys"

and then you can copy/paste the public key (the long string)

-----BEGIN CERTIFICATE-----
content of the
x5c field
-----END CERTIFICATE-----

and you can paste that into a verification window beck on the page where you copy the full token. If all goes well you should see:

That token can be used to access the backend API :). Up until this point we ware only talking about the authentication. But in the setup you ware asked to assigned some roles, now different users can have different roles, and they are not encoded in the JWT. But that is not a problem our backed (spring security) can contact the Azure AD and retrieve them and then see if the user has permission to this or that.

The code.

OK now that all is explained. Let’s do some code. The application can be found on GitHub and the link at the end.

Angular:

Install the Active Directory Authentication Library (ADAL) for Angular 6+ (Angular 6.X.X and Angular 7.X.X):

npm i microsoft-adal-angular6

and then configure MsAdalAngular6Module. We have to pass the same values that ware mentioned in the tutorial. And replace tenant and clientId.

We have to set up two more things first authentication on startup and then enrich every HTTP request with JWT Token

We achieve that by specifying HTTP_INTERCEPTORS and APP_INITIALIZER tokens (different token :) ). Then we can freely call the the backend using regular httpClient syntax available in angular.

Spring:

First notice that i had to use the 2.0.3.RELEASE of the spring boot. And that is because there is a bug in the following versions ( https://github.com/Microsoft/azure-spring-boot/issues/530) that cause error:

Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
at com.nimbusds.oauth2.sdk.util.URLUtils.serializeParameters(URLUtils.java:101) ~[oauth2-oidc-sdk-6.0.jar:6.0]
....

So if you see that downgrade.

In spring we have to duplicate the configuration a little bit. Not sure why is that but either spring or the Microsoft lib was complaining if I removed one of this.

And the configuration of the

And the rest endpoint:

as you can see user has only one role. So when you try to access one endpoint from the angular app you can see the response in the console and you get the error for the other.

Code:

--

--

piotr szybicki
12 developer labors

Piotr Szybicki’s, Programmer, Java Developer, ML Entusiast