TokenManager.vb

TokenManager.vb Copy imageCopy
Imports System.Runtime.Caching

Namespace FaxWSVBConsoleApplication
    ''' <summary>
    ''' Helper class
    ''' Implements a Cache and RestApiClient to store and request Access tokens
    ''' </summary>
    Public Class TokenManager
        Private ReadOnly cache As MemoryCache = MemoryCache.Default
        Private key As String = "tokenresponse_"
        Private authClient As RestApiClient

        Public Sub New(ByVal endpoint As String)
            authClient = New RestApiClient(endpoint)
        End Sub


        ''' <summary>
        ''' Retrieve cached AccessTokenResponse object for a user
        ''' </summary>
        ''' <param name="username"></param>
        ''' <returns></returns>        
        Public Function GetCachedAccessTokenResponse(ByVal username As String) As AccessTokenResponse
            Dim cacheKey As String = key & username
            Return TryCast(cache.Get(cacheKey), AccessTokenResponse)
        End Function


        ''' <summary>
        ''' Store or update AccessTokenResponse in cache for a user
        ''' </summary>
        ''' <param name="username"></param>
        ''' <param name="tokenResponse"></param>
        Public Sub AddOrUpdateCachedAccessTokenResponse(ByVal username As String, ByVal tokenResponse As AccessTokenResponse)
            Dim cacheKey As String = key & username

            If tokenResponse Is Nothing Then
                Return
            End If


            ' TODO: Cache can expire and cause the loss of the refresh token, which will require the need for a new access token
            cache.Set(cacheKey, tokenResponse, tokenResponse.ValidTo.Value.AddMinutes(15))
        End Sub


        ''' <summary>
        ''' Remove cached AccessTokenResponse for user
        ''' </summary>
        ''' <param name="username"></param>
        ''' <returns></returns>
        Public Function RemoveCachedAccessTokenResponse(ByVal username As String) As AccessTokenResponse
            Dim cacheKey As String = key & username
            Return TryCast(cache.Remove(cacheKey), AccessTokenResponse)
        End Function


        ''' <summary>
        ''' Request an AccessTokenResponse from Cache for a user, or if not found request from the Identity server
        ''' </summary>
        ''' <param name="request"></param>
        ''' <param name="ignoreCache"></param>
        ''' <returns></returns>
        Public Function GetAccessToken(ByVal request As AccessTokenRequest, ByVal Optional ignoreCache As Boolean = False) As AccessTokenResponse
            Dim accessTokenResponse As AccessTokenResponse = Nothing
            Dim accessTokenRequest As AccessTokenRequest = Nothing

            If Not ignoreCache Then
                ' Get Existing OAuth Access Token from Cache
                accessTokenResponse = GetCachedAccessTokenResponse(request.Username)
            End If


            ' If we have a valid token in cache attempt to use it
            If accessTokenResponse IsNot Nothing AndAlso Not accessTokenResponse.IsExpired Then
                Console.WriteLine("AccessToken found in Cache for user: {0}", request.Username)
                Return accessTokenResponse
            End If


            ' Token not found in Cache
            If accessTokenResponse Is Nothing Then
                Console.WriteLine("Calling GetAccessToken for user: {0}", request.Username)

                ' Create OAuth Token Request 
                accessTokenRequest = New AccessTokenRequest(clientId:=request.ClientId, clientSecret:=request.ClientSecret, username:=request.Username, password:=request.Password, grantType:=request.GrantType, scope:=request.Scope, redirectUri:=request.RedirectUri, code:=request.Code, refreshToken:=request.RefreshToken, state:=request.State)

                ' Get New OAuth Access Token
                accessTokenResponse = authClient.GetToken(accessTokenRequest)

                If accessTokenResponse.HttpStatusCode <> Net.HttpStatusCode.OK Then
                    Console.WriteLine("GetAccessToken Call Failed")
                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription)
                    Return accessTokenResponse
                End If


                ' Store OAuth Access Token Response to Cache for re-use
                Console.WriteLine("Storing AccessTokenResponse to Cache")
                AddOrUpdateCachedAccessTokenResponse(request.Username, accessTokenResponse)
                Return accessTokenResponse
            End If


            ' Token is expired and we don't have a refresh token
            If String.IsNullOrEmpty(accessTokenResponse.RefreshToken) Then
                Console.WriteLine("Token expired, calling GetAccessToken for user: {0}", request.Username)

                ' Create OAuth Token Request 
                accessTokenRequest = New AccessTokenRequest(clientId:=request.ClientId, clientSecret:=request.ClientSecret, username:=request.Username, password:=request.Password, grantType:=request.GrantType, scope:=request.Scope, redirectUri:=request.RedirectUri, code:=request.Code, refreshToken:=request.RefreshToken, state:=request.State)

                ' Get New OAuth Access Token
                accessTokenResponse = authClient.GetToken(accessTokenRequest)

                If accessTokenResponse.HttpStatusCode <> Net.HttpStatusCode.OK Then
                    Console.WriteLine("GetAccessToken Call Failed")
                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription)
                    Return accessTokenResponse
                End If


                ' Store OAuth Access Token Response to Cache for re-use
                Console.WriteLine("Storing AccessTokenResponse to Cache")
                AddOrUpdateCachedAccessTokenResponse(request.Username, accessTokenResponse)
                Return accessTokenResponse
            End If


            ' Token is expired and has the ability to use a refresh token
            If Not String.IsNullOrEmpty(accessTokenResponse.RefreshToken) Then
                Console.WriteLine("Token expired, calling GetRefreshToken for user: {0}, with token: {1}...", request.Username, accessTokenResponse.RefreshToken.Substring(0, 8))

                ' Create OAuth Token Refresh Request
                accessTokenRequest = New AccessTokenRequest(clientId:=request.ClientId, clientSecret:=request.ClientSecret, username:=Nothing, password:=Nothing, grantType:="refresh_token", scope:=request.Scope, redirectUri:=Nothing, code:=Nothing, refreshToken:=accessTokenResponse.RefreshToken, state:=Nothing)

                ' Get New OAuth Access token using Refresh token
                accessTokenResponse = authClient.GetToken(accessTokenRequest)

                If accessTokenResponse.HttpStatusCode <> Net.HttpStatusCode.OK Then
                    Console.WriteLine("GetRefreshToken Call Failed")
                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription)
                    Return accessTokenResponse
                End If


                ' Store OAuth Access Token Response to Cache for re-use
                Console.WriteLine("Storing AccessTokenResponse to Cache")
                AddOrUpdateCachedAccessTokenResponse(request.Username, accessTokenResponse)
                Return accessTokenResponse
            End If

            Console.WriteLine("Oops, we should not have gotten here")
            Return Nothing
        End Function
    End Class
End Namespace

RestApiClient.vb

RestApiClient.vb Copy imageCopy
Imports System.Net
Imports System.Net.Http
Imports System.Text.Json

Namespace FaxWSVBConsoleApplication
    ''' <summary>
    ''' Helper class
    ''' Talks with the Identity server
    ''' </summary>
    Public Class RestApiClient
        Implements IDisposable

#Region "Variables"
        Protected ReadOnly _oauthUrl As String
        Protected _client As HttpClient = Nothing
        Private disposed As Boolean = False


#End Region

        Public Sub New(ByVal oauthUrl As String)
            _oauthUrl = oauthUrl
            _client = New HttpClient()
        End Sub

        Public Function GetToken(ByVal request As AccessTokenRequest) As AccessTokenResponse
            Dim response As AccessTokenResponse = New AccessTokenResponse()
            Dim postResponse = PostData(_oauthUrl, request)

            Try
                response = JsonSerializer.Deserialize(Of AccessTokenResponse)(postResponse.Content)
                response.HttpStatusCode = postResponse.StatusCode
            Catch ex As Exception
                response.ErrorDescription = ex.Message
                response.Error = postResponse.Content
                response.HttpStatusCode = HttpStatusCode.InternalServerError
            End Try

            Return response
        End Function

        Private Function PostData(ByVal oauthUrl As String, ByVal request As AccessTokenRequest) As PostResponse
            Dim postResponse = New PostResponse()
            Dim data = New List(Of KeyValuePair(Of String, String))()

            ' Convert request to key value pair
            If Not String.IsNullOrEmpty(request.ClientId) Then data.Add(New KeyValuePair(Of String, String)("client_id", request.ClientId))
            If Not String.IsNullOrEmpty(request.ClientSecret) Then data.Add(New KeyValuePair(Of String, String)("client_secret", request.ClientSecret))
            If Not String.IsNullOrEmpty(request.Username) Then data.Add(New KeyValuePair(Of String, String)("username", request.Username))
            If Not String.IsNullOrEmpty(request.Password) Then data.Add(New KeyValuePair(Of String, String)("password", request.Password))
            If Not String.IsNullOrEmpty(request.Scope) Then data.Add(New KeyValuePair(Of String, String)("scope", request.Scope))
            If Not String.IsNullOrEmpty(request.GrantType) Then data.Add(New KeyValuePair(Of String, String)("grant_type", request.GrantType))
            If Not String.IsNullOrEmpty(request.RedirectUri) Then data.Add(New KeyValuePair(Of String, String)("redirect_uri", request.RedirectUri))
            If Not String.IsNullOrEmpty(request.Code) Then data.Add(New KeyValuePair(Of String, String)("code", request.Code))
            If Not String.IsNullOrEmpty(request.RefreshToken) Then data.Add(New KeyValuePair(Of String, String)("refresh_token", request.RefreshToken))
            If Not String.IsNullOrEmpty(request.State) Then data.Add(New KeyValuePair(Of String, String)("state", request.State))

            Try
                Dim response As HttpResponseMessage = Nothing

                ' Post request
                response = _client.PostAsync(oauthUrl, New FormUrlEncodedContent(data)).GetAwaiter().GetResult()

                ' Post response
                postResponse.Content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
                postResponse.StatusCode = response.StatusCode
                postResponse.ReasonPhrase = response.ReasonPhrase

                'Console.WriteLine("PostResponse: " + postResponse.Content);

                Return postResponse
            Catch ex As Exception
                postResponse.ReasonPhrase = ex.Message
                postResponse.StatusCode = HttpStatusCode.InternalServerError
                Return postResponse
            End Try
        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If disposed Then Return

            If _client IsNot Nothing Then
                _client.Dispose()
                _client = Nothing
            End If

            disposed = True
            GC.SuppressFinalize(Me)
        End Sub
    End Class
End Namespace

AccessTokenRequest.vb

AccessTokenRequest.vb Copy imageCopy
Namespace FaxWSVBConsoleApplication

    ''' <summary>
    ''' Helper class
    ''' Stores request to get an Access Token for a user from the Identity server
    ''' </summary>
    Public Class AccessTokenRequest
        Public Sub New()
            ClientId = String.Empty
            ClientSecret = String.Empty
            Username = String.Empty
            Password = String.Empty
            GrantType = String.Empty
            Scope = String.Empty
            RedirectUri = String.Empty
            Code = String.Empty
            RefreshToken = String.Empty
            State = String.Empty
        End Sub

        Public Sub New(ByVal clientId As String, ByVal clientSecret As String, ByVal username As String, ByVal password As String, ByVal grantType As String, ByVal scope As String, ByVal redirectUri As String, ByVal code As String, ByVal refreshToken As String, ByVal state As String)
            Me.ClientId = clientId
            Me.ClientSecret = clientSecret
            Me.Username = username
            Me.Password = password
            Me.GrantType = grantType
            Me.Scope = scope
            Me.RedirectUri = redirectUri
            Me.Code = code
            Me.RefreshToken = refreshToken
            Me.State = state
        End Sub

        Public Property GrantType As String
        Public Property ClientId As String
        Public Property ClientSecret As String
        Public Property Scope As String
        Public Property Username As String
        Public Property Password As String
        Public Property RedirectUri As String
        Public Property Code As String
        Public Property RefreshToken As String
        Public Property State As String
    End Class
End Namespace

AccessTokenResponse.vb

AccessTokenResponse.vb Copy imageCopy
Imports System.Net
Imports System.Text.Json.Serialization

Namespace FaxWSVBConsoleApplication
    ''' <summary>
    ''' Helper class
    ''' Stores the Access Token received from the Identity server
    ''' </summary>
    Public Class AccessTokenResponse
        Public Sub New()
            requestDate = Date.Now
            AccessToken = String.Empty
            ExpiresInSeconds = 0
            TokenType = String.Empty
            [Error] = String.Empty
            ErrorDescription = String.Empty
            HttpStatusCode = HttpStatusCode.OK
        End Sub

        Public Overrides Function ToString() As String
            Dim nl As String = Environment.NewLine
            Return "TokenType: " & TokenType & nl & _
                    "AccessToken: " & AccessToken & nl & _
                    "RefreshToken: " & RefreshToken & nl & _
                    "State: " & State & nl & _
                    "ExpiresInSeconds: " & ExpiresInSeconds & nl & _
                    "RequestDateTime: " & RequestDateTime & nl & _
                    "HttpStatusCode: " & HttpStatusCode & nl & _
                    "ErrorDescription: " & ErrorDescription & nl & _
                    "Error: " & [Error] & nl
        End Function

        <JsonPropertyName("token_type")>
        Public Property TokenType As String
        <JsonPropertyName("access_token")>
        Public Property AccessToken As String
        <JsonPropertyName("expires_in")>
        Public Property ExpiresInSeconds As Integer
        <JsonPropertyName("refresh_token")>
        Public Property RefreshToken As String
        <JsonPropertyName("error")>
        Public Property [Error] As String
        <JsonPropertyName("error_description")>
        Public Property ErrorDescription As String

        Public ReadOnly Property IsExpired As Boolean
            Get
                If ValidFrom Is Nothing OrElse ValidTo Is Nothing Then Return True
                Return ValidTo < Date.Now
            End Get
        End Property

        Private requestDate As Date?
        Public Property HttpStatusCode As HttpStatusCode
        Public ReadOnly Property RequestDateTime As Date?
            Get
                Return requestDate
            End Get
        End Property

        Public ReadOnly Property ValidFrom As Date?
            Get
                Return If(String.IsNullOrEmpty([Error]), RequestDateTime, Nothing)
            End Get
        End Property

        Public ReadOnly Property ValidTo As Date?
            Get
                Return If(ExpiresInSeconds > 0 AndAlso ValidFrom.HasValue, ValidFrom.Value.AddSeconds(ExpiresInSeconds), CType(Nothing, Date?))
            End Get
        End Property

        <JsonPropertyName("state")>
        Public Property State As String
    End Class
End Namespace

PostResponse.vb

PostResponse.vb Copy imageCopy
Imports System.Net

Namespace FaxWSVBConsoleApplication
    ''' <summary>
    ''' Helper class
    ''' Holds pieces of data from HttpMessage in RestApiClient
    ''' </summary>
    Public Class PostResponse
        Public Property Content As String
        Public Property StatusCode As HttpStatusCode
        Public Property ReasonPhrase As String
    End Class
End Namespace

FaxWSAuthWrapper.vb

FaxWSAuthWrapper.vb Copy imageCopy
Imports System.Net
Imports FaxWSVBConsoleApplication.FaxWSReference

Namespace FaxWSVBConsoleApplication
    ''' <summary>
    ''' Wrap the SOAP interface to grab additional helpful data
    ''' </summary>
    Public Class FaxWSAuthWrapper
        Inherits FaxWS

        ' Request
        Public AccessToken As String = String.Empty
        Public ClientRequestId As String = String.Empty

        ' Response
        Public ExecutionTime As Integer = -1
        Public ActivityId As String = String.Empty
        Public ResponseHeaders As WebHeaderCollection = Nothing
        Public HttpStatusCode As HttpStatusCode = HttpStatusCode.OK



        ''' <remarks/>
        Public Sub New()
        End Sub

        Protected Overrides Function GetWebRequest(ByVal uri As Uri) As WebRequest
            Dim request As HttpWebRequest = CType(MyBase.GetWebRequest(uri), HttpWebRequest)


            ' Add additional request headers
            If Not String.IsNullOrEmpty(ClientRequestId) Then
                request.Headers.Add("x-ch-request-id", ClientRequestId)
            End If


            ' Add Authorization header
            If Not String.IsNullOrEmpty(AccessToken) Then
                request.Headers.Add("Authorization", "Bearer " & AccessToken)
            End If

            Return request
        End Function


        ' Overwrite to store away http headers
        Protected Overrides Overloads Function GetWebResponse(ByVal request As WebRequest) As WebResponse
            Dim httpresponse As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
            ActivityId = String.Empty
            ExecutionTime = -1
            HttpStatusCode = httpresponse.StatusCode

            ' storeaway headers
            ResponseHeaders = httpresponse.Headers
            Dim strActivityId As String = httpresponse.GetResponseHeader("x-ch-activity-id")

            If Not String.IsNullOrEmpty(strActivityId) Then
                ActivityId = strActivityId
            End If

            Dim strResponseTime As String = httpresponse.GetResponseHeader("x-ch-execution-time")

            If Not String.IsNullOrEmpty(strResponseTime) Then
                ExecutionTime = Convert.ToInt32(strResponseTime)
            End If


            ' call base class
            Return MyBase.GetWebResponse(request)
        End Function

        Protected Overrides Overloads Function GetWebResponse(ByVal request As WebRequest, ByVal ar As IAsyncResult) As WebResponse
            Dim httpresponse As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
            ActivityId = String.Empty
            ExecutionTime = -1
            HttpStatusCode = httpresponse.StatusCode

            ' storeaway headers
            ResponseHeaders = httpresponse.Headers
            Dim strActivityId As String = httpresponse.GetResponseHeader("x-ch-activity-id")

            If Not String.IsNullOrEmpty(strActivityId) Then
                ActivityId = strActivityId
            End If

            Dim strResponseTime As String = httpresponse.GetResponseHeader("x-ch-execution-time")

            If Not String.IsNullOrEmpty(strResponseTime) Then
                ExecutionTime = Convert.ToInt32(strResponseTime)
            End If


            ' call base class
            Return MyBase.GetWebResponse(request, ar)
        End Function
    End Class
End Namespace

See Also

Other Resources