Introduction
It took me 3 days of debugging and a lot of searching / trying / experimenting to get this thing done. The documentation in Azure is not very good and I could not find any good articles on the web. But after putting together bits and pieces of information, I finally got it to work.
Background
In my case, we are developing enterprise applications and want to implement SingleSignOn
through Azure for our customer.
All users have to be registered up front by registering them in our system. This way, we know who they are and which company they belong to. This is a must for us as we are running a multitenant application where all customers are running in the same web application.
I decided not to use OWIN.OAuth
package to do the authentication as I would like to know what is happening in my application.
Using the Code
The whole point of my code is to get the JWT from Azure to find out if the user is logged in and the user name from his/her domain.
Let's start:
Here is the documentation from Microsoft that I could find on the subject.
So I followed this and met a speedbump on...
Also, take a look at my question on Stackoverflow.
{"error":"invalid_grant","error_description":
"AADSTS65001: No permission to access user information is configured for
'7686eb91-cfcd-4b89-b4db-422b52205848' application, or it is expired or revoked.
\r\nTrace ID: a631793a-a02b-4d08-abbb-bc7340a87409\r\nCorrelation ID:
335493ad-685a-4147-9e03-218a0b2acab3\r\nTimestamp: 2015-04-15 06:38:27Z",
"error_codes":[65001],"timestamp":"2015-04-15 06:38:27Z",
"trace_id":"a631793a-a02b-4d08-abbb-bc7340a87409","correlation_id"
:"335493ad-685a-4147-9e03-218a0b2acab3","submit_url":null,"context":null}
So here is my code with comments to get this to work. It's implemented in a good old .aspx page. :)
Public Class Azure
Inherits System.Web.UI.Page
Const AzureEndPoint As String = "https://login.windows.net/common/oauth2/authorize/"
Const AzureClientId As String = "7686eb91-cfcd-4b89-b4db-422b52205848"
Const AzureSecret = "1S3YAHs4UE4uIaeQavKShCn798/llcwQje4bjjpQI88="
Private Function MyUrl() As String
Return HttpContext.Current.Request.Url.Scheme & "://" & _
HttpContext.Current.Request.Url.Authority & HttpContext.Current.Request.Url.AbsolutePath
End Function
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If HttpContext.Current.Request("code") Is Nothing _
Then
SendToAzure()
Return
End If
HandleAzureResponse(HttpContext.Current.Request("code"), _
HttpContext.Current.Request("state"))
Use it to get the information about the user from Azure
End Sub
Public Sub SendToAzure()
Dim params As New Dictionary(Of String, String)
params.Add("response_type", "code")
params.Add("redirect_uri", MyUrl)
params.Add("client_id", AzureClientId)
params.Add("state", HttpContext.Current.Request.Url.Query)
Response.Redirect(AzureEndPoint & "?" & createFromDic(params))
End Sub
Public Sub HandleAzureResponse(ByVal code As String, retUrl As String)
Dim result As HttpResponseMessage
Dim params As New Dictionary(Of String, String)
params.Add("client_id", AzureClientId)
params.Add("client_secret", AzureSecret)
params.Add("code", code)
params.Add("grant_type", "authorization_code") _
at first but that did not give me the JWT back
params.Add("redirect_uri", MyUrl)
"https://petterinfotjenester.onmicrosoft.com/ITAS_SSO") _
This I found by luck somewhere on the internet.
params.Add("resource", "https://graph.windows.net")
This tella azure what kind of data you want.
Dim fromDic = createFromDic(params)
Dim content As New System.Net.Http.StringContent(fromDic, System.Text.Encoding.UTF8, _
"application/x-www-form-urlencoded")
Using wc As New HttpClient
The guid is not needed I believe so i replaced it with common.
Dim url = "common/oauth2/token"
wc.BaseAddress = New Uri("https://login.windows.net/")
result = wc.PostAsync(url, content).Result
End Using
Dim value = result.Content.ReadAsStringAsync.Result
Dim deserializeObject = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Oauth2Response)(value)
End Sub
Private Function createFromDic(ByVal dictionary As Dictionary(Of String, String)) As String
Dim ret As New List(Of String)
For Each kv In dictionary
ret.Add(kv.Key & "=" & UrlEncode(kv.Value))
Next
Return Join(ret.ToArray, "&")
End Function
End Class
Public Class Oauth2Response
Public access_token As String
Public token_type As String
Public expires_in As Integer
Private _id_token As String
Public Property id_token As String
Get
Return _id_token
End Get
Set(value As String)
idValues = value.Split("."c)
_id_token = value
End Set
End Property
Private idValues As String()
Public ReadOnly Property EncryptionMethod As String
Get
Return idValues(0)
End Get
End Property
Public ReadOnly Property AzureJwt As Azure_JWT
Get
Dim values = idValues(1)
For x = 1 To (values.Length Mod 4)
values += "="
Next
Dim fromBase64String = Convert.FromBase64String(values)
Dim getString = Encoding.UTF8.GetString(fromBase64String)
Return Newtonsoft.Json.JsonConvert.DeserializeObject(Of Azure_JWT)(getString)
End Get
End Property
Public ReadOnly Property Sig As String
Get
Return idValues(2)
End Get
End Property
End Class
Public Class Azure_JWT
Public aud As String
Public iss As String
Public iat As String
Public nbf As String
Public exp As String
Public ver As String
Public tid As String
Public oid As String
Public upn As String
Public unique_name As String
Public [sub] As String
Public family_name As String
Public given_name As String
End Class
Points of Interest
The
?api-version=1.0
parameter that is included in the URL when you copy the address from Azure must be removed. Good job, Microsoft.
You must use the:
https://graph.windows.net
As the resource parameter, this might not be that strange, but the error message in Azure is and I found no good documentation on it.
History
- 17th April, 2015: Initial version