TokenManager.cs

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

namespace IWSConsoleSample
{
    /// <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 string key = "tokenresponse_";
        private 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 IWSConsoleSample
{
    /// <summary>
    /// Helper class
    /// Talks with the Identity server
    /// </summary>
    public class RestApiClient : IDisposable
    {
        #region Variables
        protected readonly string _baseAddress;
        protected readonly string _tokenUrl;
        protected readonly string _authorizeUrl;
        protected HttpClient _client = null;
        #endregion

        public RestApiClient(string endpoint)
        {
            _baseAddress = endpoint;
            //_authorizeUrl = _baseAddress + "connect/authorize";
            _tokenUrl = "connect/token";

            _client = new HttpClient();
            _client.BaseAddress = new Uri(endpoint);
        }

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

            var postResponse = PostData(_tokenUrl, 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 relativeUrl, 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(relativeUrl, 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()
        {
            if (_client != null)
            {
                _client.Dispose();
                _client = null;
            }

        }
    }
}

PostResponse.cs

PostResponse.cs Copy imageCopy
using System.Net;

namespace IWSConsoleSample
{
    /// <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; }

    }
}

AccessTokenRequest.cs

AccessTokenRequest.cs Copy imageCopy
namespace IWSConsoleSample
{

    /// <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 = null, string code = null, string refreshToken = null, string state = null)
        {
            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 IWSConsoleSample
{
    /// <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; }

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

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

}

IWSAuthWrapper.cs

IWSAuthWrapper.cs Copy imageCopy
using IWSConsoleSample.IWSServiceReference;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace IWSConsoleSample
{
    public class IWSAuthWrapper : InboundWS
    {
        // 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 IWSAuthWrapper()
        {
        }

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

    }
}