/// <summary> /// Returns the string representation of the bewit, which is a base64 URL encoded string of format /// id\exp\mac\ext, where id is the user identifier, exp is the UNIX time until which bewit is /// valid, mac is the HMAC of the bewit to protect integrity, and ext is the application specific data. /// </summary> public string ToBewitString() { if (request.Method != HttpMethod.Get) // Not supporting HEAD throw new InvalidOperationException("Bewit not allowed for methods other than GET"); ulong now = utcNow.ToUnixTime() + Convert.ToUInt64(this.localOffset); var artifacts = new ArtifactsContainer() { Id = credential.Id, Timestamp = now + (ulong)lifeSeconds, Nonce = String.Empty, ApplicationSpecificData = this.applicationSpecificData ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); // Sign the request crypto.Sign(); // Bewit is for GET and GET must have no request body // bewit: id\exp\mac\ext string bewit = String.Format(@"{0}\{1}\{2}\{3}", credential.Id, artifacts.Timestamp, artifacts.Mac.ToBase64String(), artifacts.ApplicationSpecificData); return bewit.ToBytesFromUtf8().ToBase64UrlString(); }
/// <summary> /// Returns true if the timestamp sent in by the client is fresh subject to the /// maximum allowed skew and the adjustment offset. /// </summary> private static bool IsTimestampFresh(ulong now, ArtifactsContainer artifacts) { string offset = ConfigurationManager.AppSettings["LocalTimeOffsetMillis"]; string skew = ConfigurationManager.AppSettings["ClockSkewSeconds"]; if (offset == null || skew == null) { string message = "Required config settings of LocalTimeOffsetMillis or ClockSkewSeconds missing."; Tracing.Error(message); throw new Exception(message); } now = now + UInt64.Parse(offset); ulong shelfLife = (UInt64.Parse(skew) * 1000); var age = Math.Abs((artifacts.Timestamp * 1000.0) - now); bool isFresh = (age <= shelfLife); if (!isFresh) { Tracing.Information( String.Format("Stale Timestamp: Age {0} is more than shelf life of {1}", age, shelfLife)); } return(isFresh); }
/// <summary> /// Returns true if the timestamp sent in by the client is fresh subject to the /// maximum allowed skew and the adjustment offset. /// </summary> private static bool IsTimestampFresh(ulong now, ArtifactsContainer artifacts) { string offset = ConfigurationManager.AppSettings["LocalTimeOffsetMillis"]; string skew = ConfigurationManager.AppSettings["ClockSkewSeconds"]; if (offset == null || skew == null) { string message = "Required config settings of LocalTimeOffsetMillis or ClockSkewSeconds missing."; Tracing.Error(message); throw new Exception(message); } now = now + UInt64.Parse(offset); ulong shelfLife = (UInt64.Parse(skew) * 1000); var age = Math.Abs((artifacts.Timestamp * 1000.0) - now); bool isFresh = (age <= shelfLife); if (!isFresh) Tracing.Information( String.Format("Stale Timestamp: Age {0} is more than shelf life of {1}", age, shelfLife)); return isFresh; }
internal Cryptographer(NormalizedRequest request, ArtifactsContainer artifacts, Credential credential) { this.normalizedRequest = request; this.artifacts = artifacts; this.credential = credential; this.hasher = new Hasher(credential.Algorithm); }
/// <summary> /// Returns the string representation of the bewit, which is a base64 URL encoded string of format /// id\exp\mac\ext, where id is the user identifier, exp is the UNIX time until which bewit is /// valid, mac is the HMAC of the bewit to protect integrity, and ext is the application specific data. /// </summary> public string ToBewitString() { if (request.Method != HttpMethod.Get) // Not supporting HEAD { throw new InvalidOperationException("Bewit not allowed for methods other than GET"); } ulong now = utcNow.ToUnixTime() + Convert.ToUInt64(this.localOffset); var artifacts = new ArtifactsContainer() { Id = credential.Id, Timestamp = now + (ulong)lifeSeconds, Nonce = String.Empty, ApplicationSpecificData = this.applicationSpecificData ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); // Sign the request crypto.Sign(); // Bewit is for GET and GET must have no request body // bewit: id\exp\mac\ext string bewit = String.Format(@"{0}\{1}\{2}\{3}", credential.Id, artifacts.Timestamp, artifacts.Mac.ToBase64String(), artifacts.ApplicationSpecificData); return(bewit.ToBytesFromUtf8().ToBase64UrlString()); }
/// <summary> /// Returns the string representation of the bewit, which is a base64 URL encoded string of format /// id\exp\mac\ext, where id is the user identifier, exp is the UNIX time until which bewit is /// valid, mac is the HMAC of the bewit to protect integrity, and ext is the application specific data. /// </summary> public async Task<string> ToBewitStringAsync() { if (request.Method != HttpMethod.Get) // Not supporting HEAD throw new InvalidOperationException("Bewit not allowed for methods other than GET"); ulong now = utcNow.ToUnixTime() + UInt64.Parse(ConfigurationManager.AppSettings["LocalTimeOffsetMillis"]); var artifacts = new ArtifactsContainer() { Id = credential.Id, Timestamp = now + (ulong)lifeSeconds, Nonce = String.Empty, ApplicationSpecificData = this.applicationSpecificData ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); // Sign the request await crypto.SignAsync(null); // bewit: id\exp\mac\ext string bewit = String.Format(@"{0}\{1}\{2}\{3}", credential.Id, artifacts.Timestamp, artifacts.Mac.ToBase64String(), artifacts.ApplicationSpecificData); return bewit.ToBytesFromUtf8().ToBase64UrlString(); }
/// <summary> /// Attempts to convert the passed in header parameter (string) into the CLR equivalent, which is an /// instance of ArtifactsContainer. The return value indicates whether the conversion succeeded. /// </summary> internal static bool TryParse(string headerParameter, out ArtifactsContainer container) { ArtifactsContainer result = new ArtifactsContainer(); var keysToBeProcessed = new HashSet<string>() { ID, TS, NONCE, EXT, MAC, HASH, TSM }; var replacedString = Regex.Replace(headerParameter, PARAMETER_MATCH_PATTERN, (Match match) => { string key = match.Groups[1].Value.Trim(); string value = match.Groups[2].Value.Trim(); bool isValidValue = Regex.Match(value, VALUE_MATCH_PATTERN).Success; bool isValidKey = keysToBeProcessed.Any(k => k == key); // Key is neither duplicate nor bad if (isValidValue && isValidKey) { switch (key) { case ID: result.Id = value; break; case TS: { ulong timestamp; if (UInt64.TryParse(value, out timestamp)) { result.Timestamp = timestamp; break; } else return value; } case NONCE: result.Nonce = value; break; case EXT: result.ApplicationSpecificData = value; break; case MAC: result.Mac = value.ToBytesFromBase64(); break; case HASH: result.PayloadHash = value.ToBytesFromBase64(); break; case TSM: result.TimestampMac = value.ToBytesFromBase64(); break; } keysToBeProcessed.Remove(key); // Processed return String.Empty; } else return value; }); if (replacedString == String.Empty) // No more, no less -> valid parameter data { container = result; return true; } Tracing.Error("Unable to parse the artifacts."); container = null; return false; }
/// <summary> /// Returns true if the timestamp sent in by the client is fresh subject to the /// maximum allowed skew and the adjustment offset. /// </summary> private static bool IsTimestampFresh(ulong now, ArtifactsContainer artifacts) { now = now + UInt64.Parse(ConfigurationManager.AppSettings["LocalTimeOffsetMillis"]); ulong shelfLife = (UInt64.Parse(ConfigurationManager.AppSettings["ClockSkewSeconds"]) * 1000); var age = Math.Abs((artifacts.Timestamp * 1000.0) - now); return (age <= shelfLife); }
/// <summary> /// Returns an AuthenticationResult object corresponding to the result of authentication done /// using the client supplied artifacts in the HTTP authorization header in hawk scheme. /// </summary> /// <param name="now">Current UNIX time in milliseconds.</param> /// <param name="request">Request object.</param> /// <param name="options">Hawk authentication options</param> /// <returns></returns> internal static async Task <AuthenticationResult> AuthenticateAsync(ulong now, IRequestMessage request, Options options) { ArtifactsContainer artifacts = null; Credential credential = null; if (request.HasValidHawkScheme()) { if (ArtifactsContainer.TryParse(request.Authorization.Parameter, out artifacts)) { if (artifacts != null && artifacts.AreClientArtifactsValid) { credential = options.CredentialsCallback(artifacts.Id); if (credential != null && credential.IsValid) { var normalizedRequest = new NormalizedRequest(request, artifacts); var crypto = new Cryptographer(normalizedRequest, artifacts, credential); // Request body is needed only when payload hash is present in the request string body = null; if (artifacts.PayloadHash != null && artifacts.PayloadHash.Length > 0) { body = await request.ReadBodyAsStringAsync(); } if (crypto.IsSignatureValid(body, request.ContentType)) // MAC and hash checks { if (IsTimestampFresh(now, artifacts, options)) { // If you get this far, you are authentic. Welcome and thanks for flying Hawk! return(new AuthenticationResult() { IsAuthentic = true, Artifacts = artifacts, Credential = credential, ApplicationSpecificData = artifacts.ApplicationSpecificData }); } else { // Authentic but for the timestamp freshness. // Give a chance to the client to correct the clocks skew. var timestamp = new NormalizedTimestamp(DateTime.UtcNow, credential, options.LocalTimeOffsetMillis); request.ChallengeParameter = timestamp.ToWwwAuthenticateHeaderParameter(); } } } } } } return(new AuthenticationResult() { IsAuthentic = false }); }
/// <summary> /// Returns an AuthenticationResult object corresponding to the result of authentication done /// using the client supplied artifacts in the HTTP authorization header in hawk scheme. /// </summary> /// <param name="now">Current UNIX time in milliseconds.</param> /// <param name="request">Request object.</param> /// <param name="callback">The callback function that returns a Credential object corresponding to the identifier passed in.</param> /// <returns></returns> internal static async Task <AuthenticationResult> AuthenticateAsync(ulong now, HttpRequestMessage request, Func <string, Credential> callback) { ArtifactsContainer artifacts = null; Credential credential = null; if (request.HasValidHawkScheme()) { if (ArtifactsContainer.TryParse(request.Headers.Authorization.Parameter, out artifacts)) { if (artifacts != null && artifacts.AreClientArtifactsValid) { credential = callback(artifacts.Id); if (credential != null && credential.IsValid) { var normalizedRequest = new NormalizedRequest(request, artifacts); var crypto = new Cryptographer(normalizedRequest, artifacts, credential); if (await crypto.IsSignatureValidAsync(request.Content)) // MAC and hash checks { if (IsTimestampFresh(now, artifacts)) { // If you get this far, you are authentic. Welcome and thanks for flying Hawk! return(new AuthenticationResult() { IsAuthentic = true, Artifacts = artifacts, Credential = credential, ApplicationSpecificData = artifacts.ApplicationSpecificData }); } else { // Authentic but for the timestamp freshness. // Give a chance to the client to correct the clocks skew. var timestamp = new NormalizedTimestamp(DateTime.UtcNow, credential); request.PutChallengeParameter(timestamp.ToWwwAuthenticateHeaderParameter()); } } } } } } return(new AuthenticationResult() { IsAuthentic = false }); }
/// <summary> /// Returns true if the timestamp sent in by the client is fresh subject to the /// maximum allowed skew and the adjustment offset. /// </summary> private static bool IsTimestampFresh(ulong now, ArtifactsContainer artifacts, Options options) { now = now + Convert.ToUInt64(options.LocalTimeOffsetMillis); ulong shelfLife = (Convert.ToUInt64(options.ClockSkewSeconds) * 1000); var age = Math.Abs((artifacts.Timestamp * 1000.0) - now); bool isFresh = (age <= shelfLife); if (!isFresh) Tracing.Information( String.Format("Stale Timestamp: Age {0} is more than shelf life of {1}", age, shelfLife)); return isFresh; }
/// <summary> /// Returns true if the timestamp sent in by the client is fresh subject to the /// maximum allowed skew and the adjustment offset. /// </summary> private static bool IsTimestampFresh(ulong now, ArtifactsContainer artifacts, Options options) { now = now + Convert.ToUInt64(options.LocalTimeOffsetMillis); ulong shelfLife = (Convert.ToUInt64(options.ClockSkewSeconds) * 1000); var age = Math.Abs((artifacts.Timestamp * 1000.0) - now); bool isFresh = (age <= shelfLife); if (!isFresh) { Tracing.Information( String.Format("Stale Timestamp: Age {0} is more than shelf life of {1}", age, shelfLife)); } return(isFresh); }
internal NormalizedRequest(IRequestMessage request, ArtifactsContainer artifacts) { this.artifacts = artifacts; // Determine host and port - take the host name from X-Forwarded-For header, if present, or from // the Host header, if present, or from the HttpRequestMessage object. For bewit, it is always from URI. string firstPreference = IsBewit? null : request.ForwardedFor; string secondPreference = IsBewit ? null : request.Host; this.hostName = this.GetHostName(firstPreference, out this.port) ?? this.GetHostName(secondPreference, out this.port) ?? request.Uri.Host; if (String.IsNullOrWhiteSpace(this.port)) this.port = request.Uri.Port.ToString(); this.method = request.Method.Method.ToUpper(); this.path = request.Uri.PathAndQuery; }
internal NormalizedRequest(HttpRequestMessage request, ArtifactsContainer artifacts) { this.artifacts = artifacts; // Determine host and port - take the host name from X-Forwarded-For header, if present, or from // the Host header, if present, or from the HttpRequestMessage object. For bewit, it is always from URI. string firstPreference = IsBewit? null : this.GetXForwardedForHeaderValue(request); string secondPreference = IsBewit ? null : request.Headers.Host; this.hostName = this.GetHostName(firstPreference, out this.port) ?? this.GetHostName(secondPreference, out this.port) ?? request.RequestUri.Host; if (String.IsNullOrWhiteSpace(this.port)) { this.port = request.RequestUri.Port.ToString(); } this.method = request.Method.Method.ToUpper(); this.path = request.RequestUri.PathAndQuery; }
/// <summary> /// Returns the string representation of the bewit, which is a base64 URL encoded string of format /// id\exp\mac\ext, where id is the user identifier, exp is the UNIX time until which bewit is /// valid, mac is the HMAC of the bewit to protect integrity, and ext is the application specific data. /// </summary> public async Task <string> ToBewitStringAsync() { if (request.Method != HttpMethod.Get) // Not supporting HEAD { throw new InvalidOperationException("Bewit not allowed for methods other than GET"); } ulong now = utcNow.ToUnixTime() + UInt64.Parse(ConfigurationManager.AppSettings["LocalTimeOffsetMillis"]); var artifacts = new ArtifactsContainer() { Id = credential.Id, Timestamp = now + (ulong)lifeSeconds, Nonce = String.Empty, ApplicationSpecificData = this.applicationSpecificData ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); // Sign the request await crypto.SignAsync(null); // bewit: id\exp\mac\ext string bewit = String.Format(@"{0}\{1}\{2}\{3}", credential.Id, artifacts.Timestamp, artifacts.Mac.ToBase64String(), artifacts.ApplicationSpecificData); return(bewit.ToBytesFromUtf8().ToBase64UrlString()); }
/// <summary> /// Attempts to convert the passed in header parameter (string) into the CLR equivalent, which is an /// instance of ArtifactsContainer. The return value indicates whether the conversion succeeded. /// </summary> internal static bool TryParse(string headerParameter, out ArtifactsContainer container) { ArtifactsContainer result = new ArtifactsContainer(); var keysToBeProcessed = new HashSet <string>() { ID, TS, NONCE, EXT, MAC, HASH, TSM }; var replacedString = Regex.Replace(headerParameter, PARAMETER_MATCH_PATTERN, (Match match) => { string key = match.Groups[1].Value.Trim(); string value = match.Groups[2].Value.Trim(); bool isValidValue = Regex.Match(value, VALUE_MATCH_PATTERN).Success; bool isValidKey = keysToBeProcessed.Any(k => k == key); // Key is neither duplicate nor bad if (isValidValue && isValidKey) { switch (key) { case ID: result.Id = value; break; case TS: { ulong timestamp; if (UInt64.TryParse(value, out timestamp)) { result.Timestamp = timestamp; break; } else { return(value); } } case NONCE: result.Nonce = value; break; case EXT: result.ApplicationSpecificData = value; break; case MAC: result.Mac = value.ToBytesFromBase64(); break; case HASH: result.PayloadHash = value.ToBytesFromBase64(); break; case TSM: result.TimestampMac = value.ToBytesFromBase64(); break; } keysToBeProcessed.Remove(key); // Processed return(String.Empty); } else { return(value); } }); if (replacedString == String.Empty) // No more, no less -> valid parameter data { container = result; return(true); } Tracing.Error("Unable to parse the artifacts."); container = null; return(false); }
/// <summary> /// Creates the HTTP Authorization header in hawk scheme. /// </summary> internal async Task CreateClientAuthorizationInternalAsync(IRequestMessage request, DateTime utcNow) { var credential = options.CredentialsCallback(); this.artifacts = new ArtifactsContainer() { Id = credential.Id, Timestamp = utcNow.AddSeconds(HawkClient.CompensatorySeconds).ToUnixTime(), Nonce = NonceGenerator.Generate() }; if (options.NormalizationCallback != null) this.artifacts.ApplicationSpecificData = options.NormalizationCallback(request); var normalizedRequest = new NormalizedRequest(request, this.artifacts); this.crypto = new Cryptographer(normalizedRequest, this.artifacts, credential); // Sign the request bool includePayloadHash = options.RequestPayloadHashabilityCallback != null && options.RequestPayloadHashabilityCallback(request); string payload = includePayloadHash ? await request.ReadBodyAsStringAsync() : null; crypto.Sign(payload, request.ContentType); request.Authorization = new AuthenticationHeaderValue(HawkConstants.Scheme, this.artifacts.ToAuthorizationHeaderParameter()); }
/// <summary> /// Returns true if the server response HMAC cannot be validated, indicating possible tampering. /// </summary> private async Task<bool> IsResponseTamperedAsync(ArtifactsContainer artifacts, Cryptographer crypto, IResponseMessage response) { if (response.Headers.ContainsKey(HawkConstants.ServerAuthorizationHeaderName)) { string header = response.Headers[HawkConstants.ServerAuthorizationHeaderName].FirstOrDefault(); if (!String.IsNullOrWhiteSpace(header) && header.Substring(0, HawkConstants.Scheme.Length).ToLower() == HawkConstants.Scheme) { ArtifactsContainer serverAuthorizationArtifacts; if (ArtifactsContainer.TryParse(header.Substring(HawkConstants.Scheme.Length + " ".Length), out serverAuthorizationArtifacts)) { // To validate response, ext, hash, and mac in the request artifacts must be // replaced with the ones from the server. artifacts.ApplicationSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; artifacts.PayloadHash = serverAuthorizationArtifacts.PayloadHash; artifacts.Mac = serverAuthorizationArtifacts.Mac; // Response body is needed only if payload hash is present in the server response. string body = null; if (artifacts.PayloadHash != null && artifacts.PayloadHash.Length > 0) { body = await response.ReadBodyAsStringAsync(); } bool isValid = crypto.IsSignatureValid(body, response.ContentType); if (isValid) { string appSpecificData = serverAuthorizationArtifacts.ApplicationSpecificData; isValid = options.VerificationCallback == null || options.VerificationCallback(response, appSpecificData); } return !isValid; } } } return true; // Missing header means possible tampered response (to err on the side of caution). }
/// <summary> /// Returns true, if there is a WWW-Authenticate header containing ts and tsm but mac /// computed for ts does not match tsm, indicating possible tampering. Otherwise, returns false. /// This method also sets the compensation field so that the timestamp in the subsequent requests /// are adjusted to reduce the clock skew. /// </summary> private bool IsTimestampResponseTampered(ArtifactsContainer artifacts, IResponseMessage response) { var wwwHeader = response.WwwAuthenticate; if (wwwHeader != null) { string parameter = wwwHeader.Parameter; ArtifactsContainer timestampArtifacts; if (!String.IsNullOrWhiteSpace(parameter) && ArtifactsContainer.TryParse(parameter, out timestampArtifacts)) { var ts = new NormalizedTimestamp(timestampArtifacts.Timestamp, options.CredentialsCallback(), options.LocalTimeOffsetMillis); if (!ts.IsValid(timestampArtifacts.TimestampMac)) return true; lock (myPrecious) HawkClient.CompensatorySeconds = (int)(timestampArtifacts.Timestamp - DateTime.UtcNow.ToUnixTime()); Tracing.Information("HawkClient.CompensatorySeconds set to " + HawkClient.CompensatorySeconds); } } return false; }
/// <summary> /// Returns an AuthenticationResult object corresponding to the result of authentication done /// using the client supplied artifacts in the bewit query string parameter. /// </summary> /// <param name="bewit">Value of the query string parameter with the name of 'bewit'.</param> /// <param name="now">Date and time in UTC to be used as the base for computing bewit life.</param> /// <param name="request">Request object.</param> /// <param name="options">Hawk authentication options</param> internal static AuthenticationResult Authenticate(string bewit, ulong now, IRequestMessage request, Options options) { if (!String.IsNullOrWhiteSpace(bewit)) { if (request.Method == HttpMethod.Get) { if (options != null && options.CredentialsCallback != null) { var parts = bewit.ToUtf8StringFromBase64Url().Split('\\'); if (parts.Length == 4) { ulong timestamp = 0; if (UInt64.TryParse(parts[1], out timestamp) && timestamp * 1000 > now) { string id = parts[0]; string mac = parts[2]; string ext = parts[3]; if (!String.IsNullOrWhiteSpace(id) && !String.IsNullOrWhiteSpace(mac)) { RemoveBewitFromUri(request); Credential credential = options.CredentialsCallback(id); if (credential != null && credential.IsValid) { var artifacts = new ArtifactsContainer() { Id = id, Nonce = String.Empty, Timestamp = timestamp, Mac = mac.ToBytesFromBase64(), ApplicationSpecificData = ext ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); if (crypto.IsSignatureValid()) // Bewit is for GET and GET must have no request body { return(new AuthenticationResult() { IsAuthentic = true, Credential = credential, Artifacts = artifacts, ApplicationSpecificData = ext }); } } } } } } } } return(new AuthenticationResult() { IsAuthentic = false }); }
/// <summary> /// Returns an AuthenticationResult object corresponding to the result of authentication done /// using the client supplied artifacts in the bewit query string parameter. /// </summary> /// <param name="bewit">Value of the query string parameter with the name of 'bewit'.</param> /// <param name="now">Date and time in UTC to be used as the base for computing bewit life.</param> /// <param name="request">Request object.</param> /// <param name="options">Hawk authentication options</param> internal static AuthenticationResult Authenticate(string bewit, ulong now, IRequestMessage request, Options options) { if (!String.IsNullOrWhiteSpace(bewit)) { if (request.Method == HttpMethod.Get) { if (options != null && options.CredentialsCallback != null) { var parts = bewit.ToUtf8StringFromBase64Url().Split('\\'); if (parts.Length == 4) { ulong timestamp = 0; if (UInt64.TryParse(parts[1], out timestamp) && timestamp * 1000 > now) { string id = parts[0]; string mac = parts[2]; string ext = parts[3]; if (!String.IsNullOrWhiteSpace(id) && !String.IsNullOrWhiteSpace(mac)) { RemoveBewitFromUri(request); Credential credential = options.CredentialsCallback(id); if (credential != null && credential.IsValid) { var artifacts = new ArtifactsContainer() { Id = id, Nonce = String.Empty, Timestamp = timestamp, Mac = mac.ToBytesFromBase64(), ApplicationSpecificData = ext ?? String.Empty }; var normalizedRequest = new NormalizedRequest(request, artifacts) { IsBewit = true }; var crypto = new Cryptographer(normalizedRequest, artifacts, credential); if (crypto.IsSignatureValid()) // Bewit is for GET and GET must have no request body { return new AuthenticationResult() { IsAuthentic = true, Credential = credential, Artifacts = artifacts, ApplicationSpecificData = ext }; } } } } } } } } return new AuthenticationResult() { IsAuthentic = false }; }