public static OAuthTokenCredential GetAuthenticationInformationForDatabaseUrl(string databaseUrl, string refreshToken = null, Func <OAuthRequest, OAuthResponse> requestCallback = null) { var instanceUrl = GetInstanceUrl(databaseUrl); // deserialize token cache from file (only if file exists and has changed since last deserialization) DeserializeTokenCache(); var result = TryGetCurrentOAuthToken(instanceUrl, ref refreshToken); if (result != null) { return(result); } var authority = CreateAuthorityAsync(instanceUrl) .ConfigureAwait(false) .GetAwaiter() .GetResult(); var tokenClient = CreateTokenClient(authority); result = TryGetOAuthTokenFromRefreshTokenAsync(tokenClient, authority, refreshToken) .ConfigureAwait(false) .GetAwaiter() .GetResult(); if (result != null) { StoreToken(instanceUrl, result); return(result); } // no refresh token or refresh token expired, do the full auth cycle eventually involving a password prompt if (requestCallback != null) { var cryptoNumbers = new CryptoNumbers(); var startUrl = CreateOAuthStartUrl(authority, cryptoNumbers); var request = new OAuthRequest(startUrl, RedirectUri); var response = requestCallback(request)?.ToAuthorizeResponse(); if (response != null) { result = TryGetOAuthTokenFromAuthorizeResponseAsync(tokenClient, cryptoNumbers, response) .ConfigureAwait(false) .GetAwaiter() .GetResult(); if (result != null) { StoreToken(instanceUrl, result); return(result); } } } return(null); }
private static string CreateOAuthStartUrl(string authority, CryptoNumbers cryptoNumbers) { // FUTURE: discover authorize endpoint via ".well-known/openid-configuration" var request = new AuthorizeRequest(authority + "/connect/authorize"); return(request.CreateAuthorizeUrl( clientId: ClientId, responseType: "id_token code", responseMode: "form_post", scope: "openid profile email offline_access piweb", redirectUri: RedirectUri, state: cryptoNumbers.State, nonce: cryptoNumbers.Nonce, codeChallenge: cryptoNumbers.Challenge, codeChallengeMethod: OidcConstants.CodeChallengeMethods.Sha256)); }
public static async Task <OAuthTokenCredential> GetAuthenticationInformationForDatabaseUrlAsync(string databaseUrl, string refreshToken = null, Func <OAuthRequest, Task <OAuthResponse> > requestCallbackAsync = null) { var instanceUrl = GetInstanceUrl(databaseUrl); var result = TryGetCurrentOAuthToken(instanceUrl, ref refreshToken); if (result != null) { return(result); } var authority = await CreateAuthorityAsync(instanceUrl).ConfigureAwait(false); var tokenClient = CreateTokenClient(authority); result = await TryGetOAuthTokenFromRefreshTokenAsync(tokenClient, authority, refreshToken).ConfigureAwait(false); if (result != null) { AccessTokenCache.Store(instanceUrl, result); return(result); } // no refresh token or refresh token expired, do the full auth cycle eventually involving a password prompt if (requestCallbackAsync != null) { var cryptoNumbers = new CryptoNumbers(); var startUrl = CreateOAuthStartUrl(authority, cryptoNumbers); var request = new OAuthRequest(startUrl, RedirectUri); var response = (await requestCallbackAsync(request).ConfigureAwait(false))?.ToAuthorizeResponse(); if (response != null) { result = await TryGetOAuthTokenFromAuthorizeResponseAsync(tokenClient, cryptoNumbers, response).ConfigureAwait(false); if (result != null) { AccessTokenCache.Store(instanceUrl, result); return(result); } } } return(null); }
private static async Task <OAuthTokenCredential> TryGetOAuthTokenFromAuthorizeResponseAsync(TokenClient tokenClient, CryptoNumbers cryptoNumbers, AuthorizeResponse response) { if (response != null) { // claims des IdentityToken decodieren var claims = DecodeSecurityToken(response.IdentityToken).Claims.ToArray(); // die folgenden validierungen sind notwendig, um diversen CSRF / man in the middle / etc. Angriffsszenarien zu begegnen // state validieren if (!string.Equals(cryptoNumbers.State, response.State, StringComparison.Ordinal)) { throw new InvalidOperationException("invalid state value in openid service responce."); } // nonce validieren if (!ValidateNonce(cryptoNumbers.Nonce, claims)) { throw new InvalidOperationException("invalid nonce value in identity token."); } // c_hash validieren if (!ValidateCodeHash(response.Code, claims)) { throw new InvalidOperationException("invalid c_hash value in identity token."); } // code eintauschen gegen access token und refresh token, dabei den code verifier mitschicken, um man-in-the-middle Angriff auf authorization code zu begegnen (PKCE) var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync( code : response.Code, redirectUri : RedirectUri, codeVerifier : cryptoNumbers.Verifier).ConfigureAwait(false); if (tokenResponse.IsError) { throw new InvalidOperationException("error during request of access token using authorization code: " + tokenResponse.Error); } return(OAuthTokenCredential.CreateWithIdentityToken(tokenResponse.IdentityToken, tokenResponse.AccessToken, DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn), tokenResponse.RefreshToken)); } return(null); }