Interface Between Google Sign-in and MailKit - wpf

I am writing an app in WPF (Windows 10 desktop) that should
include a component where the user can download message headers
and messages from G-Mail.
I am trying to use MailKit to interface with G-Mail via a secure
connection (without having to turn on "allow less-secure apps"
for G-Mail) and download messages with POP3. I am a bit confused
as to the proper procedure.
FYI: I know next to nothing about OAuth and TLS, so KISS please.
I have created and downloaded a JSON file for OAuth 2.0 from Google.
I have visited the FAQ for MailKit, and the following section
seems relevant, but I'm not sure as to what I should plug in
to the interface.
(Please see the code below.)
For "password", would that be the password for the account?
I'm not sure as to what to give for
"your-developer-id#developer.gserviceaccount.com".
.........................................................
https://github.com/jstedfast/MailKit/blob/master/FAQ.md#ProtocolLog
.........................................................
From the Q & A:
How can I log in to a GMail account using OAuth 2.0?
The first thing you need to do is follow Google's instructions for
obtaining OAuth 2.0 credentials for your application.
Once you've done that, the easiest way to obtain an access token is to
use Google's Google.Apis.Auth library:
var certificate = new X509Certificate2 (#"C:\path\to\certificate.p12", "password",
X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential (new ServiceAccountCredential
.Initializer ("your-developer-id#developer.gserviceaccount.com") {
// Note: other scopes can be found here: [links]
Scopes = new[] { "https mail google com " },
User = "username#gmail.com"
}.FromCertificate (certificate));
bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);
// Note: result will be true if the access token was received successfully
// Now that you have an access token (credential.Token.AccessToken), you can
// use it with MailKit as if it were the password:
using (var client = new ImapClient ()) {
client.Connect ("imap.gmail.com", 993, true);
// use the access token as the password string
client.Authenticate ("username#gmail.com", credential.Token.AccessToken);
}
My next question: Would the user be able to access their own account(s)
with my app without having to follow the same procedure?
IOW: Will the credentials that I've downloaded work for any account?
... or allow access only to the account from which the credentials
were created?
If the credentials are only good for my own account, then I'll have to
do something else.
Would Google Sign-In be a better approach?
I've downloaded the example code for .NET from Google:
https://github.com/googlesamples/oauth-apps-for-windows
I've built and ran ran "OAuthConsoleApp", as well as "OAuthDesktopApp".
It would seem that I am getting a secure connection from those,
as I have gotten the following output:
.........................................................
redirect URI: http 127.0.0.1:64003
Listening..
Authorization code: qwerty ...
Exchanging code for tokens...
Send the request ...
GetRequestStream ...
await stream.WriteAsync ...
Get the response ...
responseText ...
{
"access_token": "qwerty ...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "qwerty ...",
"id_token": "qwerty ..."
}
Making API Call to Userinfo...
+++ userinfoResponseText : {
"sub": "117108120545711995673",
"name": "My Name",
"given_name": "My",
"family_name": "Name",
"picture": "qwerty ...",
"locale": "en"
}
.....................................................
I see that I have an "access_token" in the response and I thought
that I could plug that in to the "client.Authenticate" method for
MailKit as the password (as mentioned in the docs for MailKit):
string access_token = tokenEndpointDecoded["access_token"];
client.Connect ("pop.gmail.com", 995, SecureSocketOptions.SslOnConnect);
client.Authenticate ("username#gmail.com", access_token);
It threw an exception:
.....................................................
"POP3 server did not respond with a +OK response to the AUTH command."
at MailKit.Net.Pop3.Pop3Client.Authenticate(Encoding encoding,
ICredentials credentials, CancellationToken cancellationToken)
at MailKit.MailService.Authenticate(String userName, String
password, CancellationToken cancellationToken)
at
NS_MailKit_01.Pop3.cls_mailKit_Pop3_01.connect_and_authenticate(Object
p3_client, String p_access_token)
in :\Software_Develpoment_Sys_03_K\MIME_EMail\TEST_02\Mail_Kit_01\MailKit_01.cs:line
465
at
LIB1_01_G_Mail_Auth.cls_G_mail_authorization.str_token_NTRF.invoke_access_token(String
p_access_token)
in K:\Software_Develpoment_Sys_03_K\MIME_EMail\TEST_02\OAuth\oauth-apps-for-windows\OAuthConsoleApp\LIB1_01_G_Mail_Auth\G_Mail_Auth_01.cs:
line 95
at
LIB1_01_G_Mail_Auth.cls_G_mail_authorization.d__13.MoveNext()
in K:\Software_Develpoment_Sys_03_K\MIME_EMail\TEST_02\OAuth\oauth-apps-for-windows\OAuthConsoleApp\LIB1_01_G_Mail_Auth\G_Mail_Auth_01.cs:line
343
.....................................................
Does anyone know how I could get a "credential" object from
the Google interface that I could use with MailKit?
Any help would be appreciated.
Thanks!

For "password", would that be the password for the account?
No. It would be the password for your PKCS12 file containing your X.509 Certificate and your private key.
I'm not sure as to what to give for "your-developer-id#developer.gserviceaccount.com".
You need to register yourself and your application with Google's Developer program which will give you a developer id to use. You need to follow their directions.

Related

Firebase Auth Apple Sign In for Web "INVALID_CLIENT" error?

I'm using firebase to implement Sign in with Apple on my React web application. I have created an AppID, Service ID with Sign in with Apple enabled. Created a Key, configured and completed the required steps as outlined here. However I still get the following error response when signing in with apple. I have implemented both Facebook and Google signin with no issues.
{
"error": {
"code": 400,
"message": "INVALID_IDP_RESPONSE : Error getting access token from https://appleid.apple.com, OAuth2 redirect uri is: https://rideup-984a6.firebaseapp.com/__/auth/handler, response: OAuth2TokenResponse{params: error=invalid_client, httpMetadata: HttpMetadata{status=400, cachePolicy=NO_CACHE, cacheDurationJava=null, cacheImmutable=false, staleWhileRevalidate=null, filename=null, lastModified=null, retryAfter=null, headers=HTTP/1.1 200 OK\r\n\r\n, contentSecurityPolicies=[], originTrials=[], cookieList=[]}}",
"errors": [
{
"message": "INVALID_IDP_RESPONSE : Error getting access token from https://appleid.apple.com, OAuth2 redirect uri is: https://rideup-984a6.firebaseapp.com/__/auth/handler, response: OAuth2TokenResponse{params: error=invalid_client, httpMetadata: HttpMetadata{status=400, cachePolicy=NO_CACHE, cacheDurationJava=null, cacheImmutable=false, staleWhileRevalidate=null, filename=null, lastModified=null, retryAfter=null, headers=HTTP/1.1 200 OK\r\n\r\n, contentSecurityPolicies=[], originTrials=[], cookieList=[]}}",
"domain": "global",
"reason": "invalid"
}
]
}
}
Please help me solve this isssue.
I was also facing this issue.
For me the error message "invalid_client" was a little bit misleading.
By following the docs of firebase on how to setup the apple signin method, you also need to create a private key in the apple developer console and paste it into your firebase settings.
What it fixed for me was, that I provided the private key Name instead of the key ID in the firebase settings.
You can find the Key ID, besides the name, in the detail view of the key, you created.
Key ID in Firebase Settings

Service account request to IAP-protected app results in 'Invalid GCIP ID token: JWT signature is invalid'

I am trying to programmatically access an IAP-protected App Engine Standard app via Python from outside of the GCP environment.
I have tried various methods, including the method shown in the docs here: https://cloud.google.com/iap/docs/authentication-howto#iap-make-request-python. Here is my code:
from google.auth.transport.requests import Request
from google.oauth2 import id_token
import requests
def make_iap_request(url, client_id, method='GET', **kwargs):
"""Makes a request to an application protected by Identity-Aware Proxy.
Args:
url: The Identity-Aware Proxy-protected URL to fetch.
client_id: The client ID used by Identity-Aware Proxy.
method: The request method to use
('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
**kwargs: Any of the parameters defined for the request function:
https://github.com/requests/requests/blob/master/requests/api.py
If no timeout is provided, it is set to 90 by default.
Returns:
The page body, or raises an exception if the page couldn't be retrieved.
"""
# Set the default timeout, if missing
if 'timeout' not in kwargs:
kwargs['timeout'] = 90
# Obtain an OpenID Connect (OIDC) token from metadata server or using service
# account.
open_id_connect_token = id_token.fetch_id_token(Request(), client_id)
print(f'{open_id_connect_token=}')
# Fetch the Identity-Aware Proxy-protected URL, including an
# Authorization header containing "Bearer " followed by a
# Google-issued OpenID Connect token for the service account.
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
print(f'{resp=}')
if resp.status_code == 403:
raise Exception('Service account does not have permission to '
'access the IAP-protected application.')
elif resp.status_code != 200:
raise Exception(
'Bad response from application: {!r} / {!r} / {!r}'.format(
resp.status_code, resp.headers, resp.text))
else:
return resp.text
if __name__ == '__main__':
res = make_iap_request(
'https://MYAPP.ue.r.appspot.com/',
'Client ID from IAP>App Engine app>Edit OAuth Client>Client ID'
)
print(res)
When I run it locally, I have the GOOGLE_APPLICATION_CREDENTIALS environment variable set to a local JSON credential file containing the keys for the service account I want to use. I have also tried running this in Cloud Functions so it would presumably use the metadata service to pick up the App Engine default service account (I think?).
In both cases, I am able to generate a token that appears valid. Using jwt.io, I see that it contains the expected data and the signature is valid. However, when I make a request to the app using the token, I always get this exception:
Bad response from application: 401 / {'X-Goog-IAP-Generated-Response': 'true', 'Date': 'Tue, 09 Feb 2021 19:25:43 GMT', 'Content-Type': 'text/html', 'Server': 'Google Frontend', 'Content-Length': '47', 'Alt-Svc': 'h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"'} / 'Invalid GCIP ID token: JWT signature is invalid'
What could I be doing wrong?
The solution to this problem is to exchange the Google Identity Token for an Identity Platform Identity Token.
The reason for the error Invalid GCIP ID token: JWT signature is invalid is caused by using a Google Identity Token which is signed by a Google RSA private key and not by a Google Identity Platform RSA private key. I overlooked GCIP in the error message, which would have told me the solution once we validated that the token was not corrupted in use.
In the question, this line of code fetches the Google Identity Token:
open_id_connect_token = id_token.fetch_id_token(Request(), client_id)
The above line of code requires that Google Cloud Application Default Credentials are setup. Example: set GOOGLE_APPLICATION_CREDENTIALS=c:\config\service-account.json
The next step is to exchange this token for an Identity Platform token:
def exchange_google_id_token_for_gcip_id_token(google_open_id_connect_token):
SIGN_IN_WITH_IDP_API = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp'
API_KEY = '';
url = SIGN_IN_WITH_IDP_API + '?key=' + API_KEY;
data={
'requestUri': 'http://localhost',
'returnSecureToken': True,
'postBody':'id_token=' + google_open_id_connect_token + '&providerId=google.com'}
try:
resp = requests.post(url, data)
res = resp.json()
if 'error' in res:
print("Error: {}".format(res['error']['message']))
exit(1)
# print(res)
return res['idToken']
except Exception as ex:
print("Exception: {}".format(ex))
exit(1)
The API Key can be found in the Google Cloud Console -> Identity Platform. Top right "Application Setup Details". This will show the apiKey and authDomain.
More information can be found at this link:
Exchanging a Google token for an Identity Platform token

Configuring Auth0 authentication on Google App Engine Standard and the Cloud Endpoints Frameworks with Python

I am using Cloud Endpoints Frameworks with Python in a Google Cloud App Engine Standard environment to provide an API.
As far as I can tell, I should be able to use python decorators from the Endpoints Frameworks in combination with the endpointscfg.py command-line tool to automatically set up token-based authentication with Auth0; the endpointscfg.py command-line automatically creates the openapi.json file that is used to configure the Google Endpoints proxy.
Here's an example of my decorator for an API that echos stuff back:
# # [START echo_api]
#endpoints.api(
name='echo',
version=_VERSION,
api_key_required=True,
audiences={'auth0': ['https://echo.<my-project>.appspot.com/_ah/api/echo/v1/echo']},
issuers={'auth0': endpoints.Issuer(
'https://<my-project>.auth0.com',
'https://<my-project>.auth0.com/.well-known/jwks.json')}
)
class EchoApi(remote.Service):
...
When I run the endpointscfg.py command-line tool, I get something in my openapi.json file that looks about right:
"paths": {
"/echo/v1/echo": {
"post": {
"operationId": "EchoApi_echo",
"parameters": [
{
"in": "body",
"name": "body",
"schema": {
"$ref": "#/definitions/MainEchoRequest"
}
}
],
"responses": {
"200": {
"description": "A successful response",
"schema": {
"$ref": "#/definitions/MainEchoResponse"
}
}
},
"security": [
{
"api_key": [],
"auth0_jwt": []
}
]
}
}
"securityDefinitions": {
"api_key": {
"in": "query",
"name": "key",
"type": "apiKey"
},
"auth0_jwt": {
"authorizationUrl": "https://<my-project>.auth0.com/authorize",
"flow": "implicit",
"type": "oauth2",
"x-google-issuer": "https://<my-project>.auth0.com",
"x-google-jwks_uri": "https://<my-project>.auth0.com/.well-known/jwks.json",
"x-google-audiences": "https://echo.<my-project>.appspot.com/_ah/api/echo/v1/echo"
}
}
So, the problem is that this set-up appears to do nothing and does not check incoming tokens to prevent access if no token is present or if the token is invalid.
I have been able to set-up manual processing of the bearer token within the API echo function using the python-jose library (sorry if it's not well done, but I'm just testing and comments are welcome):
authorization_header = self.request_state.headers.get('authorization')
if authorization_header is not None:
if authorization_header.startswith('Bearer '):
access_token = authorization_header[7:]
logging.info(access_token)
else:
logging.error("Authorization header did not start with 'Bearer '!")
raise endpoints.UnauthorizedException(
"Authentication failed (improperly formatted authorization header).")
else:
logging.error("Authorization header did not start with 'Bearer '!")
raise endpoints.UnauthorizedException("Authentication failed (bearer token not found).")
r = urlfetch.fetch(_JWKS_URL)
jwks_content = json.loads(r.content)
keys = jwks_content['keys']
public_key = jwk.construct(keys[0])
logging.info(public_key)
message, encoded_signature = str(access_token).rsplit('.', 1)
# decode the signature
decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))
# verify the signature
if not public_key.verify(message.encode("utf8"), decoded_signature):
logging.warning('Signature verification failed')
raise endpoints.UnauthorizedException("Authentication failed (invalid signature).")
else:
logging.info('Signature successfully verified')
claims = jwt.get_unverified_claims(access_token)
# additionally we can verify the token expiration
if time.time() > claims['exp']:
logging.warning('Token is expired')
raise endpoints.UnauthorizedException("Authentication failed (token expired).")
# and the Audience (use claims['client_id'] if verifying an access token)
if claims['aud'] != _APP_CLIENT_ID:
logging.warning('Token was not issued for this audience')
raise endpoints.UnauthorizedException("Authentication failed (incorrect audience).")
# now we can use the claims
logging.info(claims)
This code works, but I assumed that the whole point of setting up the decorator and configuring the openapi.json file was to off-load these checks to the proxy so that only valid tokens hit my code.
What am I doing wrong?
UPDATE:
It may be that I need to check endpoints.get_current_user() in my code to control access. However, I have just noticed the following in my logs:
Cannot decode and verify the auth token. The backend will not be able to retrieve user info (/base/data/home/apps/e~<my-project>/echo:alpha23.414400469228485401/lib/endpoints_management/control/wsgi.py:643)
Traceback (most recent call last):
File "/base/data/home/apps/e~<my-project>/echo:alpha23.414400469228485401/lib/endpoints_management/control/wsgi.py", line 640, in __call__
service_name)
File "/base/data/home/apps/e~<my-project>/echo:alpha23.414400469228485401/lib/endpoints_management/auth/tokens.py", line 75, in authenticate
error)
UnauthenticatedException: (u'Cannot decode the auth token', UnauthenticatedException(u'Cannot find the `jwks_uri` for issuer https://<my-project>.auth0.com/: either the issuer is unknown or the OpenID discovery failed',))
However, I think everything is configured ok. Any idea why 'jwks_uri' cannot be found despite the fact that path in the openapi.json file is correct?
I'm the current maintainer of these Frameworks. You do need to check endpoints.get_current_user() to control access, yes. I'm working on a feature to make this much simpler.
As for that UnauthenticatedException, you can ignore it. That's coming from the 'management framework', which attempts to check auth tokens even though it's not involved in the Frameworks' oauth security (only the api key security).

Invalid redirect_uri: http://localhost:5001/signin-oidc

I developed my IdentityServer using the netcoreapp1.1 framework, IdentityServer4 version 1.52, with a console client the connection works fine, but with an MVC client netcoreapp2.0, within the records shows me:
error: IdentityServer4.Validation.AuthorizeRequestValidator [0] Redirect_uri inválido: http://localhost:5001/signin-oidc --code omitted-- error: IdentityServer4.Endpoints.AuthorizeEndpoint [0] Request validation error
I did the steps that are in api acces hybrid but I still have the same problem, for the client that I am working in the mvc-hybrid for an asp.net core 2 application without success, something that stands out is that the AllowedRedirectUris parameter is empty.
fail: IdentityServer4.Validation.AuthorizeRequestValidator[0] Invalid redirect_uri: http://localhost:5001/signin-oidc { "ClientId": "client-hugo", "RedirectUri": "http://localhost:5001/signin-oidc", "AllowedRedirectUris": [], "SubjectId": "anonymous", "RequestedScopes": "", "Raw": { "client_id": "client-hugo", "redirect_uri": "http://localhost:5001/signin-oidc", "response_type": "code id_token", "scope": "openid profile email api-alumnos offline_access", "response_mode": "form_post", "nonce": "636412201009966900.ZDNmYjdmZWMtNWNlMS00ZDQyLWIxMjMtNWIzYTM4M2FhZmRhMmMxZGE2ZDUtM2M0MS00ZThiLTk4M2ItNDk2NGQ5YmZlODFj", "state": "CfDJ8J0brcCMivFPtUfbYRpOjISliA92IArZsJS1dmagQ9jYdvpmVj2ABROstPNhJyCWx8q4SntL6PXRubMgGqeUfdqWF0mNRAYifGy8OuEPZSQT295vWVfyN5gGhuvB6jQ043D14yUPLwkhS29IYfMoiliLQGatygTVnGMVJ7Bo-aV7FJcpHit-9b3RHRyaHiE0tToZdP8NxJInJ4vthIlXw1rgLjOmSqPxeN9KDfLgWrpki7XoBLkmV2K7px_GWp0ebaitkxnXTzDPg82M-nRJWfYLAXJ1nGvoZLD3GIXqFV22hDm0wmygbmRaMKezwZmHY12qr2pwa1N22aifJTZFzPY", "x-client-SKU": "ID_NET", "x-client-ver": "2.1.4.0" } } fail: IdentityServer4.Endpoints.AuthorizeEndpoint[0] Request validation failed
What do you recommend me to do?
If you are using identity server 4, check your ClientRedirectUris table, check if your client has permission to redirect to your url. if not add new entry.
following figure shows the table:
Make sure your mvc client is running on http://localhost:5001
I was forced to change the url from http://localhost:5001/clientA to http://localhost:5001 in the hosting.json file. Otherwise I needed to use the UsePathBase middleware which mess up the redirect URL since it always adds the path to each incoming request.

How to configure IdentityServerAuthenticationOptions.Authority to use wildcards

I successfully setup IdentityServer4 with ASP.NET Core.
As a default config I had this:
IdentityServerAuthenticationOptions options = new IdentityServerAuthenticationOptions()
{
Authority = "http://localhost:5000",
ScopeName = "scope",
ScopeSecret = "ScopeSecret",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
};
Now, using this guide I configured to be read from configuration files and so they can be any numbers in production.
For example if I setup API to be running at http://*:5000 then the client can connect to it via the service IP address like http://192.168.1.100:5000.
Once the client obtains the Bearer token and tries to use it, an Internal Server Error occures with this exception:
Unable to obtain configuration from:
'http://*:5000/.well-known/openid-configuration'.
---> System.IO.IOException: IDX10804: Unable to retrieve document from: 'http://*:5000/.well-known/openid-configuration'.
---> System.UriFormatException: Invalid URI: The hostname could not be parsed.
What is the correct way to configure IdS4 to have dynamic authority?
Update
It seems the problem is with Issuer, any idea on this?
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidIssuerException:
IDX10205: Issuer validation failed. Issuer: 'http://192.168.1.100:5000'. Did not match: validationParameters.ValidIssuer: 'http://localhost:5000' or validationParameters.ValidIssuers: 'null'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateIssuer(String issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
By a big surprise, all I needed, was to set a value (almost any value) for IssuerUri:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
////...
var identiyBuilder = services.AddIdentityServer(options =>
{
options.RequireSsl = false;
options.IssuerUri = "MyCompany";
});
////...
}
Now, by the above config, I can use the service by any IP address.
I didn't find I could just put in MyCompany
But in my log files I had the following:
Bearer was not authenticated. Failure message: IDX10205: Issuer validation failed. Issuer: 'https://crm.example.com'. Did not match: validationParameters.ValidIssuer: 'MyCompany' or validationParameters.ValidIssuers: 'null'.
I don't quite know what 'issuer' means but I was able to just take 'https://crm.example.com' and get things working with this :
options.IssuerUri = "https://crm.example.com";

Resources