This topic contains the following sections.
TokenManager.vb
TokenManager.vb | ![]() |
---|---|
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 | ![]() |
---|---|
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 | ![]() |
---|---|
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 | ![]() |
---|---|
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 | ![]() |
---|---|
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 | ![]() |
---|---|
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 |