Search code examples
vb.netmicrosoft-graph-apionedrive

Microsoft Graph / OneDrive requesting multiple authentications


I'm trying to write code to read files in a OneDrive folder of mine and, in the future, create, move and delete them.

The scrap of code below works, but it requests a new authentication (shows Microsoft's OAuth window) for each iteration in the For Each ... Next.

What am I doing wrong here?

Imports Microsoft.Graph

Public Class FormGraphClient
    Private Shared client As GraphServiceClient

    Private Async Sub FormGraphClient_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        client = AuthenticationHelper.GetAuthenticatedClient

        Dim formsmonitor_items = Await client.Me.Drive.Root.ItemWithPath("FormsMonitor").Children.Request.GetAsync

        Dim forms As New Dictionary(Of String, String)

        For Each form In formsmonitor_items
            If form.Name Like "*.json" Then
                Using formstream = Await client.Me.Drive.Items.Item(form.Id).Content.Request.GetAsync
                    Using reader = New IO.StreamReader(formstream)
                        forms(form.Name) = reader.ReadToEnd
                    End Using
                End Using
            End If
        Next

    End Sub
End Class

I'm using also this helper class:

Imports System.Net.Http.Headers
Imports Microsoft.Graph
Imports Microsoft.Identity.Client

Public Class AuthenticationHelper
    Shared clientId As String = "my-client-id"
    Public Shared Scopes As String() = {"User.Read", "Files.ReadWrite.All"}
    Public Shared IdentityClientApp As PublicClientApplication = New PublicClientApplication(clientId)
    Public Shared TokenForUser As String = Nothing
    Public Shared Expiration As DateTimeOffset
    Private Shared graphClient As GraphServiceClient = Nothing

    Public Shared Function GetAuthenticatedClient() As GraphServiceClient
        If graphClient Is Nothing Then

            Try
                graphClient = New GraphServiceClient(
                    "https://graph.microsoft.com/v1.0",
                    New DelegateAuthenticationProvider(
                        Async Function(requestMessage)
                            Dim token = Await GetTokenForUserAsync()
                            requestMessage.Headers.Authorization = New AuthenticationHeaderValue("bearer", token)
                            requestMessage.Headers.Add("SampleID", "uwp-csharp-apibrowser-sample")
                        End Function))
                Return graphClient
            Catch ex As Exception
                Debug.WriteLine("Could not create a graph client: " & ex.Message)
            End Try
        End If

        Return graphClient
    End Function

    Public Shared Async Function GetTokenForUserAsync() As Task(Of String)
        Dim authResult As AuthenticationResult

        Dim ex As Exception = Nothing

        Try
            authResult = Await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.GetUser("[email protected]"))
            TokenForUser = authResult.AccessToken
        Catch ex
            If TokenForUser Is Nothing OrElse Expiration <= DateTimeOffset.UtcNow.AddMinutes(5) Then
            End If
        End Try
        If ex IsNot Nothing Then
            Try
                authResult = Await IdentityClientApp.AcquireTokenAsync(Scopes)
                TokenForUser = authResult.AccessToken
                Expiration = authResult.ExpiresOn
            Catch ex
            End Try
        End If

        Return TokenForUser
    End Function

End Class

Solution

  • I checked up samples here and there, and came up with this wrapper class. I think I'll work on it more, but for the moment, it solved the problem mentioned at this post:

    Imports System.Net.Http
    Imports System.Net.Http.Headers
    Imports Microsoft.Graph
    Imports Microsoft.Identity.Client
    
    Public Class MsGraph
        Private Const baseUrl As String = "https://graph.microsoft.com/v1.0"
        Private ReadOnly client_id As String
        Private ReadOnly scopes As String()
        Private authSuccess As Boolean
        Private clientApp As PublicClientApplication
    
        Public Sub New(app_client_id As String, ParamArray app_scopes As String())
            client_id = app_client_id
            If Not app_scopes.Contains("User.Read", StringComparer.InvariantCultureIgnoreCase) Then
                app_scopes = {"User.Read"}.Concat(app_scopes).ToArray
            End If
            scopes = app_scopes
            clientApp = New PublicClientApplication(client_id)
            Dim authProvider = New DelegateAuthenticationProvider(AddressOf AuthenticateRequestAsync)
            Try
                Client = New GraphServiceClient(baseUrl, authProvider)
            Catch ex As Exception
                Stop
            End Try
        End Sub
    
        Public ReadOnly Property Client As GraphServiceClient
    
        Public Async Function AuthenticateRequestAsync(request As HttpRequestMessage) As Task
            request.Headers.Authorization = New AuthenticationHeaderValue("bearer", Await GetTokenForUserAsync())
        End Function
    
        Private Async Function GetTokenForUserAsync() As Task(Of String)
            Dim success As Boolean
            Dim authResult As AuthenticationResult = Nothing
            If clientApp.Users.Any Then
                Try
                    authResult = Await clientApp.AcquireTokenSilentAsync(scopes, clientApp.Users.FirstOrDefault())
                    success = True
                Catch ex As Exception
                    Stop
                End Try
            Else
                Try
                    authResult = Await clientApp.AcquireTokenAsync(scopes)
                    success = True
                Catch ex As Exception
                    Stop
                End Try
            End If
            authSuccess = success
            Return authResult?.AccessToken
        End Function
    End Class