TokenManager.cs

TokenManager.cs Copy imageCopy
using System;
using System.Runtime.Caching;

namespace FaxWSConsoleApplication
{
    /// <summary>
    /// Helper class
    /// Implements a Cache and RestApiClient to store and request Access tokens
    /// </summary>
    public class TokenManager
    {
        private readonly MemoryCache cache = MemoryCache.Default;
        private readonly string key = "tokenresponse_";
        private readonly RestApiClient authClient;

        public TokenManager(string endpoint)
        {
            authClient = new RestApiClient(endpoint);
        }

        /// <summary>
        /// Retrieve cached AccessTokenResponse object for a user
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public AccessTokenResponse GetCachedAccessTokenResponse(string username)
        {
            string cacheKey = key + username;

            return cache.Get(cacheKey) as AccessTokenResponse;
        }

        /// <summary>
        /// Store or update AccessTokenResponse in cache for a user
        /// </summary>
        /// <param name="username"></param>
        /// <param name="tokenResponse"></param>
        public void AddOrUpdateCachedAccessTokenResponse(string username, AccessTokenResponse tokenResponse)
        {
            string cacheKey = key + username;

            if(tokenResponse == null)
            {
                return;
            }

            // 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));
        }

        /// <summary>
        /// Remove cached AccessTokenResponse for user
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public AccessTokenResponse RemoveCachedAccessTokenResponse(string username)
        {
            string cacheKey = key + username;

            return cache.Remove(cacheKey) as AccessTokenResponse;
        }

        /// <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 AccessTokenResponse GetAccessToken(AccessTokenRequest request, bool ignoreCache = false)
        {
            AccessTokenResponse accessTokenResponse = null;
            AccessTokenRequest accessTokenRequest = null;

            if(!ignoreCache)
            {
                // Get Existing OAuth Access Token from Cache
                accessTokenResponse = GetCachedAccessTokenResponse(request.Username);
            }

            // If we have a valid token in cache attempt to use it
            if(accessTokenResponse != null && !accessTokenResponse.IsExpired)
            {
                Console.WriteLine("AccessToken found in Cache for user: {0}", request.Username);

                return accessTokenResponse;
            }

            // Token not found in Cache
            if(accessTokenResponse == null)
            {
                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 != System.Net.HttpStatusCode.OK)
                {
                    Console.WriteLine("GetAccessToken Call Failed");

                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription);

                    return accessTokenResponse;
                }

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

                return accessTokenResponse;
            }

            // Token is expired and we don't have a refresh token
            if(string.IsNullOrEmpty(accessTokenResponse.RefreshToken))
            {
                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 != System.Net.HttpStatusCode.OK)
                {
                    Console.WriteLine("GetAccessToken Call Failed");

                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription);

                    return accessTokenResponse;
                }

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

                return accessTokenResponse;
            }

            // Token is expired and has the ability to use a refresh token
            if(!string.IsNullOrEmpty(accessTokenResponse.RefreshToken))
            {
                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: null,
                    password: null,
                    grantType: "refresh_token",
                    scope: request.Scope,
                    redirectUri: null,
                    code: null,
                    refreshToken: accessTokenResponse.RefreshToken,
                    state: null);

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

                if(accessTokenResponse.HttpStatusCode != System.Net.HttpStatusCode.OK)
                {
                    Console.WriteLine("GetRefreshToken Call Failed");

                    Console.WriteLine("HttpStatusCode: {0}, Error Code: {1}, Error Description: {2}", accessTokenResponse.HttpStatusCode, accessTokenResponse.Error, accessTokenResponse.ErrorDescription);

                    return accessTokenResponse;
                }

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

                return accessTokenResponse;
            }

            Console.WriteLine("Oops, we should not have gotten here");
            return null;
        }
    }
}

RestApiClient.cs

RestApiClient.cs Copy imageCopy
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;

namespace FaxWSConsoleApplication
{
    /// <summary>
    /// Helper class
    /// Talks with the Identity server
    /// </summary>
    public class RestApiClient : IDisposable
    {
        #region Variables
        protected readonly string _oauthUrl;
        protected HttpClient _client = null;
        private bool disposed;
        #endregion

        public RestApiClient(string oauthUrl)
        {
            _client = new HttpClient();
            _oauthUrl = oauthUrl;
        }

        public AccessTokenResponse GetToken(AccessTokenRequest request)
        {
            AccessTokenResponse response = new AccessTokenResponse();

            var postResponse = PostData(_oauthUrl, request);

            try
            {
                response = JsonSerializer.Deserialize<AccessTokenResponse>(postResponse.Content);
                response.HttpStatusCode = postResponse.StatusCode;
            }
            catch(Exception ex)
            {
                response.ErrorDescription = ex.Message;
                response.Error = postResponse.Content;
                response.HttpStatusCode = HttpStatusCode.InternalServerError;
            }

            return response;
        }

        private PostResponse PostData(string url, AccessTokenRequest request)
        {
            var postResponse = new PostResponse();
            var data = new List<KeyValuePair<string, string>>();

            // Convert request to key value pair
            if(!string.IsNullOrEmpty(request.ClientId)) data.Add(new KeyValuePair<string, string>("client_id", request.ClientId));
            if(!string.IsNullOrEmpty(request.ClientSecret)) data.Add(new KeyValuePair<string, string>("client_secret", request.ClientSecret));
            if(!string.IsNullOrEmpty(request.Username)) data.Add(new KeyValuePair<string, string>("username", request.Username));
            if(!string.IsNullOrEmpty(request.Password)) data.Add(new KeyValuePair<string, string>("password", request.Password));
            if(!string.IsNullOrEmpty(request.Scope)) data.Add(new KeyValuePair<string, string>("scope", request.Scope));
            if(!string.IsNullOrEmpty(request.GrantType)) data.Add(new KeyValuePair<string, string>("grant_type", request.GrantType));
            if(!string.IsNullOrEmpty(request.RedirectUri)) data.Add(new KeyValuePair<string, string>("redirect_uri", request.RedirectUri));
            if(!string.IsNullOrEmpty(request.Code)) data.Add(new KeyValuePair<string, string>("code", request.Code));
            if(!string.IsNullOrEmpty(request.RefreshToken)) data.Add(new KeyValuePair<string, string>("refresh_token", request.RefreshToken));
            if(!string.IsNullOrEmpty(request.State)) data.Add(new KeyValuePair<string, string>("state", request.State));

            try
            {
                HttpResponseMessage response = null;

                // Post request
                response = _client.PostAsync(url, 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(Exception ex)
            {
                postResponse.ReasonPhrase = ex.Message;
                postResponse.StatusCode = HttpStatusCode.InternalServerError;

                return postResponse;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if(disposed) return;

            if(disposing && _client != null)
            {
                _client.Dispose();
                _client = null;
            }

            disposed = true;
        }
    }
}

AccessTokenRequest.cs

AccessTokenRequest.cs Copy imageCopy
namespace FaxWSConsoleApplication
{

    /// <summary>
    /// Helper class
    /// Stores request to get an Access Token for a user from the Identity server
    /// </summary>
    public class AccessTokenRequest
    {
        public AccessTokenRequest()
        {
            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;
        }

        public AccessTokenRequest(string clientId, string clientSecret, string username, string password, string grantType, string scope,
            string redirectUri, string code, string refreshToken, string state)
        {
            ClientId = clientId;
            ClientSecret = clientSecret;
            Username = username;
            Password = password;
            GrantType = grantType;
            Scope = scope;
            RedirectUri = redirectUri;
            Code = code;
            RefreshToken = refreshToken;
            State = state;
        }

        public string GrantType { get; set; }
        public string ClientId { get; set; }
        public string ClientSecret { get; set; }
        public string Scope { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string RedirectUri { get; set; }
        public string Code { get; set; }
        public string RefreshToken { get; set; }
        public string State { get; set; }

    }
}

AccessTokenResponse.cs

AccessTokenResponse.cs Copy imageCopy
using System;
using System.Net;
using System.Text.Json.Serialization;

namespace FaxWSConsoleApplication
{
    /// <summary>
    /// Helper class
    /// Stores the Access Token received from the Identity server
    /// </summary>
    public class AccessTokenResponse
    {
        public AccessTokenResponse()
        {
            requestDateTime = DateTime.Now;
            AccessToken = string.Empty;
            ExpiresInSeconds = 0;
            TokenType = string.Empty;
            Error = string.Empty;
            ErrorDescription = string.Empty;
            HttpStatusCode = System.Net.HttpStatusCode.OK;
        }

        public override string ToString()
        {
            string nl = 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;
        }

        [JsonPropertyName("token_type")]
        public string TokenType { get; set; }
        [JsonPropertyName("access_token")]
        public string AccessToken { get; set; }
        [JsonPropertyName("expires_in")]
        public int ExpiresInSeconds { get; set; }
        [JsonPropertyName("refresh_token")]
        public string RefreshToken { get; set; }
        [JsonPropertyName("error")]
        public string Error { get; set; }
        [JsonPropertyName("error_description")]
        public string ErrorDescription { get; set; }
        public bool IsExpired
        {
            get
            {
                if(ValidFrom == null || ValidTo == null)
                    return true;

                return ValidTo < DateTime.Now;
            }
        }

        public HttpStatusCode HttpStatusCode { get; set; }

        private DateTime? requestDateTime;
        public DateTime? RequestDateTime { get { return requestDateTime; } }
        public DateTime? ValidFrom { get { return (String.IsNullOrEmpty(Error) ? RequestDateTime : null); } }
        public DateTime? ValidTo
        {
            get
            {
                return (ExpiresInSeconds > 0 && ValidFrom.HasValue)
                    ? ValidFrom.Value.AddSeconds(ExpiresInSeconds)
                    : (DateTime?)null;
            }
        }

        [JsonPropertyName("state")]
        public string State { get; set; }
    }

}

PostResponse.cs

PostResponse.cs Copy imageCopy
using System.Net;

namespace FaxWSConsoleApplication
{
    /// <summary>
    /// Helper class
    /// Holds pieces of data from HttpMessage in RestApiClient
    /// </summary>
    public class PostResponse
    {
        public string Content { get; set; }
        public HttpStatusCode StatusCode { get; set; }
        public string ReasonPhrase { get; set; }

    }
}

FaxWSAuthWrapper.cs

FaxWSAuthWrapper.cs Copy imageCopy
using FaxWSConsoleApplication.FaxWSReference;
using System;
using System.Net;


namespace FaxWSConsoleApplication
{
    /// <summary>
    /// Wrap the SOAP interface to grab additional helpful data
    /// </summary>
    public class FaxWSAuthWrapper : FaxWS
    {
        // Request
        public string AccessToken = string.Empty;
        public string ClientRequestId = string.Empty;

        // Response
        public int ExecutionTime = -1;
        public string ActivityId = string.Empty;
        public WebHeaderCollection ResponseHeaders = null;
        public HttpStatusCode HttpStatusCode = HttpStatusCode.OK;


        /// <remarks/>
        public FaxWSAuthWrapper()
        {
        }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(uri);

            // Add additional request headers
            if(!string.IsNullOrEmpty(ClientRequestId))
            {
                request.Headers.Add("x-ch-request-id", ClientRequestId);
            }

            // Add Authorization header
            if(!string.IsNullOrEmpty(AccessToken))
            {
                request.Headers.Add("Authorization", "Bearer " + AccessToken);
            }

            return request;
        }

        // Overwrite to store away http headers
        protected override WebResponse GetWebResponse(WebRequest request)
        {
            HttpWebResponse httpresponse = (HttpWebResponse)request.GetResponse();
            ActivityId = string.Empty;
            ExecutionTime = -1;
            HttpStatusCode = httpresponse.StatusCode;

            // storeaway headers
            ResponseHeaders = httpresponse.Headers;

            string strActivityId = httpresponse.GetResponseHeader("x-ch-activity-id");

            if(!string.IsNullOrEmpty(strActivityId))
            {
                ActivityId = strActivityId;
            }

            string strResponseTime = httpresponse.GetResponseHeader("x-ch-execution-time");

            if(!string.IsNullOrEmpty(strResponseTime))
            {
                ExecutionTime = Convert.ToInt32(strResponseTime);
            }

            // call base class
            return base.GetWebResponse(request);
        }

        protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult ar)
        {
            HttpWebResponse httpresponse = (HttpWebResponse)request.GetResponse();
            ActivityId = string.Empty;
            ExecutionTime = -1;
            HttpStatusCode = httpresponse.StatusCode;

            // storeaway headers
            ResponseHeaders = httpresponse.Headers;

            string strActivityId = httpresponse.GetResponseHeader("x-ch-activity-id");

            if(!string.IsNullOrEmpty(strActivityId))
            {
                ActivityId = strActivityId;
            }

            string strResponseTime = httpresponse.GetResponseHeader("x-ch-execution-time");

            if(!string.IsNullOrEmpty(strResponseTime))
            {
                ExecutionTime = Convert.ToInt32(strResponseTime);
            }

            // call base class
            return base.GetWebResponse(request, ar);
        }

    }
}

See Also

Other Resources