public static async Task <BoolValue <JsonWebKeySet> > DownloadAsync(string url) { using var client = new HttpClient(); using var message = new HttpRequestMessage(HttpMethod.Get, url); message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/jwk-set+json")); try { var response = await client.SendAsync(message); if (!response.IsSuccessStatusCode) { return(BoolValue <JsonWebKeySet> .Fail($"Error connecting to {url}: {response.ReasonPhrase}")); } var content = await response.Content.ReadAsStringAsync(); var jsonWebKeySet = JsonConvert.DeserializeObject <JsonWebKeySet>(content); return(BoolValue <JsonWebKeySet> .Success(jsonWebKeySet)); } catch (Exception ex) { Console.WriteLine(ex); throw; } }
public async Task <BoolValue <ClaimsPrincipal> > ValidateAsync(string idToken, JwtTokenValidationOptions options = null) { try { var discoveryDocument = await DiscoveryDocument.DownloadAsync(idToken); if (!discoveryDocument) { return(BoolValue <ClaimsPrincipal> .Fail(discoveryDocument.Message, discoveryDocument.Exception)); } var jwks = await JsonWebKeySet.DownloadAsync(discoveryDocument.Value.JwksUri); if (!jwks) { return(BoolValue <ClaimsPrincipal> .Fail(jwks.Message, jwks.Exception)); } options ??= new JwtTokenValidationOptions(); var parameters = options.ToTokenValidationParameters( new JwtSecurityToken(idToken), discoveryDocument.Value, jwks.Value); var handler = new JwtSecurityTokenHandler(); handler.InboundClaimTypeMap.Clear(); var user = handler.ValidateToken(idToken, parameters, out _); return(BoolValue <ClaimsPrincipal> .Success(user)); } catch (Exception ex) { return(BoolValue <ClaimsPrincipal> .Fail(ex.Message, ex)); } }
/// <inheritdoc /> public override async Task <BoolValue <AuthResult> > GetAccessTokenAsync(bool allowCached = true) { if (allowCached) { var cached = await tryGetCachedAuthResultAsync(); if (cached && cached.Value.AccessToken != null) { return(onAuthorizationDone(cached)); } } #if DEBUG var simulatedAuth = await AuthSimulator.TryGetSimulatedAccessTokenAsync(Config, CacheKey); if (simulatedAuth) { return(simulatedAuth); } #endif try { LogDebug("---- START - Tetra Pak Code Grant Flow ----"); return(await acquireTokenAsyncUsingNativeWebUI()); } catch (Exception ex) { LogError(ex, ex.Message); LogDebug("---- END - Tetra Pak Code Grant Flow ----"); return(BoolValue <AuthResult> .Fail("Could not acquire an access token", ex)); } }
public async Task <BoolValue <string[]> > TeyGetUserInfoTypes() { if (AccessToken is null) { _log?.Warning("Cannot get user information without a valid access token"); return(BoolValue <string[]> .Fail()); } try { if (_userInformation is { }) { return(BoolValue <string[]> .Success(_userInformation.Types)); } var discoDoc = DiscoveryDocument.Current; if (discoDoc is null) { var gotDiscoDoc = await DiscoveryDocument.TryDownloadAndSetCurrent(this, true); if (!gotDiscoDoc) { _log?.Debug("ERROR: Failed to retrieve the discovery document. Cannot resolve user information endpoint"); return(BoolValue <string[]> .Fail("Failed to retrieve the discovery document. Cannot resolve user information endpoint")); } discoDoc = gotDiscoDoc.Value; } _userInfoLoader ??= new UserInfoLoader(AccessToken, discoDoc, _log); _userInformation = await _userInfoLoader.AwaitDownloadedAsync(); return(BoolValue <string[]> .Success(_userInformation.Types)); }
public static BoolValue <DiscoveryEndpoint> TryParseUrl(string input) { var success = Uri.TryCreate(input, UriKind.Absolute, out var uri); if (success == false) { var msg = $"Malformed URL: {input}"; return(BoolValue <DiscoveryEndpoint> .Fail(msg, new FormatException(msg))); } if (!IsValidScheme(uri)) { var msg = $"Invalid scheme in URL: {input}"; return(BoolValue <DiscoveryEndpoint> .Fail(msg, new InvalidOperationException(msg))); } var url = input.RemoveTrailingSlash(); var authority = url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase) ? url.Substring(0, url.Length - WellKnownEndpoint.Length - 1) : url; url = url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase) ? url : $"{url.EnsureEndsWith("/")}{WellKnownEndpoint}"; return(BoolValue <DiscoveryEndpoint> .Success(new DiscoveryEndpoint(authority, url))); /* obsolete * return url.EndsWith(WellKnownEndpoint, StringComparison.OrdinalIgnoreCase) * ? new DiscoveryEndpoint(url.Substring(0, url.Length - WellKnownEndpoint.Length - 1), url) * : new DiscoveryEndpoint(url, $"{url.EnsureEndsWith("/")}{WellKnownEndpoint}"); */ }
async Task <BoolValue <AuthResult> > buildAuthResultAsync(string responseText) { var dict = JsonSerializer.Deserialize <Dictionary <string, string> >(responseText); if (!dict.TryGetValue("access_token", out var accessToken)) { return(BoolValue <AuthResult> .Fail("Could not get a valid access token.")); } var tokens = new List <TokenInfo>(); var expires = dict.TryGetValue("expires_in", out var exp) && int.TryParse(exp, out var seconds) ? DateTime.Now.AddSeconds(seconds - 4) : (DateTime?)null; tokens.Add(new TokenInfo(accessToken, TokenRole.AccessToken, expires, null)); if (dict.TryGetValue("refresh_token", out var refreshToken)) { tokens.Add(new TokenInfo(refreshToken, TokenRole.RefreshToken, null, null)); } if (!dict.TryGetValue("id_token", out var idToken)) { return(await cacheAuthResultAsync(BoolValue <AuthResult> .Success(new AuthResult(Config, Log, tokens.ToArray())))); } /* obsolete (since we made validation available we no longer auto-validates id token) * var idTokenValidation = await validateIdTokenAsync(idToken); * if (!idTokenValidation) * return BoolValue<AuthResult>.Fail(idTokenValidation.Message); */ tokens.Add(new TokenInfo(idToken, TokenRole.IdToken, null, validateIdTokenAsync)); return(await cacheAuthResultAsync(BoolValue <AuthResult> .Success(new AuthResult(Config, Log, tokens.ToArray())))); }
/// <inheritdoc /> public override async Task <BoolValue <AuthResult> > GetAccessTokenSilentlyAsync() { if (!IsCaching) { return(await GetAccessTokenAsync()); } var cached = await tryGetCachedAuthResultAsync(); if (!cached) { return(await GetAccessTokenAsync(false)); } if (cached.Value.AccessToken != null) { if (!cached.Value.Expires.HasValue || DateTime.Now < cached.Value.Expires.Value) { return(onAuthorizationDone(cached)); } } await removeFromCacheAsync(); if (string.IsNullOrEmpty(cached.Value.RefreshToken)) { return(await GetAccessTokenAsync()); } #if DEBUG var simulatedAuth = await AuthSimulator.TryGetSimulatedRenewedAccessTokenAsync(cached.Value.RefreshToken, Config, CacheKey); if (simulatedAuth) { return(simulatedAuth); } #endif // access token has expired, try renew from refresh token if available ... LogDebug("---- START - Tetra Pak Refresh Token Flow ----"); BoolValue <AuthResult> result; try { result = await acquireRenewedAccessTokenAsync(cached.Value.RefreshToken); } catch (Exception ex) { LogError(ex, ex.Message); return(BoolValue <AuthResult> .Fail("Could not renew access token", ex)); } finally { LogDebug("---- END - Tetra Pak Refresh Token Flow ----"); } return(result ? result : await GetAccessTokenAsync()); }
public static async Task <BoolValue <AuthResult> > TryGetSimulatedAccessTokenAsync(AuthConfig config, string cacheKey) { if (!IsSimulating) { return(BoolValue <AuthResult> .Fail()); } return(await GetAccessTokenAsync(config, cacheKey)); }
static async Task <BoolValue <string> > validateIdTokenAsync(string idToken) { var validator = new IdTokenValidator(); var validated = await validator.ValidateAsync(idToken); return(validated ? BoolValue <string> .Success(idToken) : BoolValue <string> .Fail(validated.Message, validated.Exception)); }
async Task <BoolValue <AuthResult> > acquireTokenAsyncUsingNativeWebUI() { LogDebug("[GET AUTH CODE BEGIN]"); LogDebug($"Listens for callbacks on {Config.RedirectUri} ..."); var authAppDelegate = Authorization.GetAuthorizingAppDelegate(); if (authAppDelegate is null) { LogDebug("Authorization fails: Could not get an authorization app delegate"); return(BoolValue <AuthResult> .Fail($"Cannot obtain a {typeof(IAuthorizingAppDelegate)}.")); } var callbackHandler = (TetraPakAuthCallbackHandler)DependencyService.Get <IAuthCallbackHandler>(); callbackHandler.NotifyUriCallback(onUriCallback); // make the call for auth code and await callback from redirect ... var authState = new AuthState(Config.IsStateUsed, Config.IsPkceUsed, Config.ClientId); var authRequest = buildAuthRequest(authState); LogDebug(authRequest); await authAppDelegate.OpenInDefaultBrowserAsync(new Uri(authRequest), Config.RedirectUri); var callback = await _authCodeTcs.Task.ConfigureAwait(false); LogDebug($"Callback notified with value: {callback.Value}"); // check the PKCE and get the access code ... var authCode = callback.Value.TryGetQueryValue("code").Value; var inState = callback.Value.TryGetQueryValue("state").Value; LogDebug("[GET AUTH CODE END]"); if (authState.IsUsed && inState != authState.State) { return(BoolValue <AuthResult> .Fail($"Returned state was invalid: \"{inState}\". Expected state: \"{authState.State}\"")); } LogDebug("[GET ACCESS CODE BEGIN]"); var accessCodeResult = await getAccessCode(authCode, authState); LogDebug("[GET ACCESS CODE END]"); return(onAuthorizationDone(accessCodeResult)); void onUriCallback(Uri uri, out bool isHandled) { if (!uri.Scheme.Equals(Config.RedirectUri.Scheme) || !uri.Authority.Equals(Config.RedirectUri.Authority)) { isHandled = false; return; } isHandled = true; _authCodeTcs.SetResult(BoolValue <Uri> .Success(uri)); } }
protected override BoolValue <string> OnValidateValue(string value) { value = value?.Trim(); if (string.IsNullOrEmpty(value) && IsRequired) { return(BoolValue <string> .Fail("This value is required")); } return(BoolValue <string> .Success(value)); }
public static Task <BoolValue <AuthResult> > TryGetFromRefreshTokenAsync(this AuthConfig config, string refreshToken) { if (!s_authResults.TryGetValue(refreshToken, out var authResult)) { return(Task.FromResult <BoolValue <AuthResult> >(BoolValue <AuthResult> .Fail())); } s_authResults.Remove(refreshToken); return(Task.FromResult(BoolValue <AuthResult> .Success(authResult))); }
async Task <BoolValue <AuthResult> > tryGetCachedAuthResultAsync() { if (!IsCaching) { return(BoolValue <AuthResult> .Fail()); } var cached = await Config.TokenCache.TryGetAsync(CacheKey); return(cached ? cached : BoolValue <AuthResult> .Fail()); }
protected override BoolValue <string> OnValidateValue(string value) { value = value?.Trim(); if (string.IsNullOrEmpty(value)) { return(base.OnValidateValue(value)); } return(!Uri.TryCreate(value, UriKind.Absolute, out _) ? BoolValue <string> .Fail("Invalid absolute URI") : BoolValue <string> .Success(value)); }
static BoolValue <DiscoveryEndpoint> tryResolveUrlFromAssumedJwtToken(string input) { try { var jwtToken = new JwtSecurityToken(input); return(TryParseUrl(jwtToken.Issuer)); } catch (Exception ex) { return(BoolValue <DiscoveryEndpoint> .Fail($"Cannot resolve discovery endpoint from: \"{input}\"", ex)); } }
public static async Task <BoolValue <AuthResult> > TryGetSimulatedRenewedAccessTokenAsync(string refreshToken, AuthConfig config, string cacheKey) { var canBeRefreshed = await config.TryGetFromRefreshTokenAsync(refreshToken); if (!canBeRefreshed) { return(BoolValue <AuthResult> .Fail($"Invalid refresh token")); } var accessToken = new TokenInfo(new RandomString(), TokenRole.AccessToken, DateTime.Now.Add(AccessTokenLongevity), null); return(await config.CacheAsync(new AuthResult(null, null, accessToken), cacheKey)); }
/// <summary> /// Attempts obtaining user information. /// </summary> /// <returns></returns> /// <remarks> /// </remarks> public async Task <BoolValue <UserInformation> > TryGetUserInformationAsync() { _log?.Debug("[GET USER INFORMATION BEGIN]"); if (AccessToken is null) { _log?.Warning("Cannot get user information without a valid access token"); return(BoolValue <UserInformation> .Fail()); } if (_userInformation != null) { _log?.Debug("User information was cached"); return(BoolValue <UserInformation> .Success(_userInformation)); } try { _log?.Debug("Retrieves user information from API ..."); var discoDoc = DiscoveryDocument.Current; if (discoDoc is null) { var gotDiscoDoc = await DiscoveryDocument.TryDownloadAndSetCurrent(this, true); if (!gotDiscoDoc) { _log?.Debug("ERROR: Failed to retrieve the discovery document. Cannot resolve user information endpoint"); return(BoolValue <UserInformation> .Fail("Failed to retrieve the discovery document. Cannot resolve user information endpoint")); } discoDoc = gotDiscoDoc.Value; } _userInfoLoader ??= new UserInfoLoader(AccessToken, discoDoc, _log); _userInformation = await _userInfoLoader.AwaitDownloadedAsync(); _log?.Debug("Successfully received user information from API"); return(BoolValue <UserInformation> .Success(_userInformation)); } catch (Exception ex) { const string Message = "Failed while retrieving user information from API"; _log?.Error(ex, Message); return(BoolValue <UserInformation> .Fail(Message, ex)); } finally { _log?.Debug("[GET USER INFORMATION END]"); } }
async Task <BoolValue <AuthResult> > getAccessCode(string authCode, AuthState authState) { var body = buildTokenRequestBody(authCode, authState); var uri = Config.TokenIssuer.AbsoluteUri; var request = (HttpWebRequest)WebRequest.Create(uri); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; var bodyData = Encoding.ASCII.GetBytes(body); request.ContentLength = bodyData.Length; Log.DebugWebRequest(request, body); var stream = request.GetRequestStream(); await stream.WriteAsync(bodyData, 0, bodyData.Length); stream.Close(); Log?.DebugWebRequest(request, body); try { var response = await request.GetResponseAsync(); var responseStream = response.GetResponseStream() ?? throw new Exception("Unexpected error: No response when requesting token."); using (var r = new StreamReader(responseStream)) { var text = await r.ReadToEndAsync(); Log?.DebugWebResponse(response as HttpWebResponse, text); return(await buildAuthResultAsync(text)); } } catch (WebException webException) { var response = (HttpWebResponse)webException.Response; return(BoolValue <AuthResult> .Fail($"Server error: {(int)response.StatusCode} {response.StatusCode.ToString().SplitCamelCase()}")); } catch (Exception ex) { return(BoolValue <AuthResult> .Fail($"Unexpected Server error: {ex}")); } }
async Task <BoolValue <AuthResult> > acquireRenewedAccessTokenAsync(string refreshToken) { var body = makeRefreshTokenBody(refreshToken, Config.IsPkceUsed); var uri = Config.TokenIssuer; var request = (HttpWebRequest)WebRequest.Create(uri); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; var bodyData = Encoding.ASCII.GetBytes(body); request.ContentLength = bodyData.Length; var stream = request.GetRequestStream(); await stream.WriteAsync(bodyData, 0, bodyData.Length); stream.Close(); Log?.DebugWebRequest(request, body); try { var response = await request.GetResponseAsync(); var responseStream = response.GetResponseStream() ?? throw new Exception("Unexpected error: No response when requesting token."); using (var r = new StreamReader(responseStream)) { var text = await r.ReadToEndAsync(); Log.DebugWebResponse(response as HttpWebResponse, text); return(await buildAuthResultAsync(text)); } } catch (Exception ex) { LogDebug($"Failed request"); return(BoolValue <AuthResult> .Fail("Could not get a valid access token.", ex)); } }