I use Office.js's getCallbackTokenAsync to load the token to pass to the backend.
In the backend, I use EWS to retrieve the email data. Here is how I perform the authentication.
ExchangeService service = new ExchangeService();
service.Url = new Uri(ewsUrl); //retrieved from getCallbackTokenAsync
service.Credentials = new OAuthCredentials(ewsToken);// retrieved from getCallbackTokenAsync
It works well in exchange online enviroment. However when tested in on-premise exchange server, I got this authentication error:
Error Message: The remote server returned an error: (401) Unauthorized.
Stack Trace: at System.Net.HttpWebRequest.GetResponse() at Microsoft.Exchange.WebServices.Data.EwsHttpWebRequest.Microsoft.Exchange.WebServices.Data.IEwsHttpWebRequest.GetResponse() at Microsoft.Exchange.WebServices.Data.ServiceRequestBase.GetEwsHttpWebResponse(IEwsHttpWebRequest request)
I then found this doc says the Oauth2 authentication is only for exchange online. I guess I need to use this NTLM (Exchange on-premises only).
The major reason I use EWS instead of Graph or Rest is it supports on-premise server more naturally. So should I change the authentication here? I certainly do not want to ask user for username and password.
Related
We would like to enable WeChat Login on our iOS client that is connected to a Parse Server backend on Heroku. From reading through the PFFacebookAuthenticationProvider, it seems that we need to write a custom authentication provider for WeChat.
WeChat Login is based on OAuth 2.0. It works as followed:
1. From our app, an authorization request is sent to the WeChat app installed on the same phone. WeChat app is called to the foreground.
2. After user approved the authorization request, a code (NOT the access token) is sent to our app.
3. With the code and our app id and app secret, our server can then call WeChat API and get the appropriate user id and access token from WeChat. This step has to happen on our server, as we cannot include the app secret within our client app.
On the WeChat documentation, it is strongly recommended that we keep the access token strictly in the control of server (anyone with the access token can make requests to WeChat API and it will be counted towards the usage limit for our API calls).
If we are to follow this practice, we cannot save the access token in the authData field of the user. Would it be acceptable to save only the code and id from WeChat into the authData and save the access token to another class that only the master key has access to? This obviously requires us to write a custom AuthAdapter for the Parse Server.
Or is there a better way to implement this custom auth? The custom auth documentation for Parse Server is pretty thin and I plan to improve it after I can get it working for myself.
You can definitely update the auth adapter to exchange the code for an access token server side. The logic would be similar to other adapters, failing to login/signup if the server is unable to process the code to access token exchange.
Here
https://github.com/parse-community/parse-server/blob/master/src/Adapters/Auth/wechat.js#L7
If the authData object has that code, you can add additional logic to exchange it.
I am trying to implement server-to-server integration with Dynamics CRM Online 2016 and BizTalk 2013 R2. I am using the WebHttpBinding to call the CRM web API, which requires a bearer token supplied as an http header:
Authorization: Bearer [base64string]
I have written a client message inspector which calls Azure AD using ADAL to acquire an access token. This is secured with a client assertion certificate, which is assigned to the registered app in our AD tenant:
var token = context.AcquireTokenAsync(this.ResourceUri, assertionCert).Result;
ResourceUri is https://[myorganisation].crm4.dynamics.com
assertionCert is a ClientAssertionCertificate created using the app registration application ID and an x509 certificate in the machine certificate store that is registered to the app as a KeyCredential
This 'works' in that it returns a token and I can decode this token to inspect the claims - there are a fair number of them, I have no way of telling whether this is the set of claims that CRM requires.
The AD app registration is configured with delegated permissions to the CRM instance.
I have set the application ID in the CRM local user to that of the app registration.
Upon calling the webAPI and supplying this token, CRM responds with 401 unauthorized.
I have repeated the same process in a powershell script and in PostMan, all of which appear to show the same behaviour.
What else am I supposed to do to make CRM accept my access token?
edit #1: Tried hardcoding the authority URI to https://login.windows.net/[my-tenant-id]/oauth2/token rather than what comes out of dynamically acquiring the authority through AuthenticationParameters - this is the same value except ending with /authorization instead of /token. This makes zero difference.
edit #2: An administrator I am working with pointed out to me that the application user I am expecting to use had no user roles assigned - this has been amended to have a role which should allow API access, but this also made no difference.
edit #3: Set oauth2AllowImplicitFlow to true in the manifest for the app registration. This doesn't make any difference.
edit #4: Made some progress by creating a new app registration, this time as a Native app rather than a web app. I managed to get a token using a client secret, and this was accepted - BUT when assigning a certificate to the app, and presenting a ClientAssertionCertificate as before, I get the response from the authority:
Error validating credentials. AADSTS50012: Client is public so a client_assertion' should not be presented.
WHY? What does 'Client is public' mean? Just work!
Hrrrmph!
Turns out that the original situation I had tried and failed with, now works.
Web application registration with delegated permissions to CRM Online
Install a client certificate on the client machine, and register this same certificate to the app using New-AzureADApplicationKeyCredential
Link the app registration to a CRM Application User created for this purpose (they are fundamentally different to interactive users) - n.b. this screen is not easy to find
Call AcquireTokenAsync() from ADAL
Just works
I am at a loss to explain why this didn't work the first time I tried it, as CRM doesn't supply any information as to why token validation failed.
We have an MVC app that connects to the Exchange server. We used to connect to an on premises server using this code to create the service:
if (string.IsNullOrEmpty(Current.UserPassword))
{
throw new UnauthorizedAccessException("Exchange access requires Authentication by Password");
}
return new ExchangeService
{
Credentials = new NetworkCredential(Current.User.LoginName, Current.UserPassword),
Url = new Uri(ConfigurationManager.AppSettings["ExchangeServiceUrl"]),
};
This worked fine, but now our IT department is migrating the Exchange server to the cloud, and some users are on the cloud server while others are on premises. So I changed the code to this:
if (string.IsNullOrEmpty(Current.UserPassword))
{
throw new UnauthorizedAccessException("Exchange access requires Authentication by Password");
}
var user = ConfigurationManager.AppSettings["ExchangeUser"];
var password = ConfigurationManager.AppSettings["ExchangePassword"];
var exchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP2)
{
Credentials = new NetworkCredential(user, password),
};
exchangeService.AutodiscoverUrl(Current.EmaiLuser + "#calamos.com", RedirectionCallback);
exchangeService.Credentials = new NetworkCredential(Current.EmaiLuser + "#calamos.com", Current.UserPassword);
return exchangeService;
I am using a service account to do the autodiscovery ( for some reason it doesn't work with a regular account) and then I am changing the credentials of the service to the user that logs in, so he can access the inbox. The problem is that , randomly, the server returns "The request failed. The remote server returned an error: (401) Unauthorized.".
I asked the IT department to check the Exchange logs, but there is nothing there about this error, so I don't know how to fix it...
So by cloud do you mean Office365 ?
I am using a service account to do the autodiscovery ( for some reason it doesn't work with a regular account)
For the users in the cloud you need to ensure the request are sent to the cloud servers maybe enable tracing https://msdn.microsoft.com/en-us/library/office/dd633676(v=exchg.80).aspx and then have a look at where the failed requests are being routed. From what you are saying your discovery is going to always point to your internal servers which is why the request will fail for the cloud based users. You need to have a way of identifying the users that are in the cloud and I would suggest you then just use the single Office365 Endpoint (eg you don't need Autodiscover for that) https//outlook.office365.com/EWS/Exchange.asmx
I'm working on a intranet-based application and I want to use Google services. Currently I have successfully implemented Google Authentication with "Sign-In for Websites" using JavaScript client-side authentication. My users can now sign in or sign up with their Google accounts.
Now I want to use Google API to create and share Google Sheets with my users. These documents will be created with a specific Google account and then shared with my users.
This is why I want to use this server-slide flow to get a one-time authorization code and exchange it for a refresh token:
https://developers.google.com/identity/sign-in/web/server-side-flow
This refresh token will be stored in my database allowing me to user Google services on behalf of this offline user.
Using JavaScript library, I was able to get the one-time authorization code that I send to my server with a AJAX request.
auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(grantOfflineAccessCallback);
var grantOfflineAccessCallback = function(authResult) {
var auth_code = authResult.code;
// Exchange the one-time authorization code for tokens
$.post(...);
}
On server-side I use Google API PHP Client (v2.0.0-RC6) to acquire an access and refresh token.
$this->client = new Google_Client();
$this->client->setClientId($this->clientId);
$this->client->setClientSecret($this->clientSecret);
$this->client->setAccessType('offline');
$this->client->setApprovalPrompt('force');
$response = $this->client->fetchAccessTokenWithAuthCode($oneTimeCode);
I wasn't able to exchange the authorization code.
Client error: `POST https://www.googleapis.com/oauth2/v4/token` resulted in a `400 Bad Request` response:
{
"error": "invalid_request",
"error_description": "Missing parameter: redirect_uri"
}
On this page we can read:
On the server, exchange the auth code for access and refresh tokens.
Use the access token to call Google APIs on behalf of the user.
On the JAVA example code:
REDIRECT_URI: // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
Because the application I working on is an intranet application, I tried to specify an empty string for this redirect_uri parameter before calling fetchAccessTokenWithAuthCode() method:
$this->client->setRedirectUri('');
... result in Redirect URI must be absolute.
Can we use this hybrid server-slide flow without callback URL?
Is there any solution to my problem?
Thanks,
Edit:
redirect_uri is where the user will be redirected to after he signed in. This URL must be registered in the Google Project (developers console). So redirect_uri is NOT the callback...!
Problem is now solved with:
$this->client->setRedirectUri('http://same.url.as.in.developers.console/');
I've created a WebAPI OData 3.0 web service with an OWIN middleware, which is configured for authentication with Windows Azure Active Directory.
The ODataControllers are marked with an [Authorize] attribute, and the IAppBuilder is configured as follows:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
ida:Tenant is my Windows Azure tenancy, and ida:Audience is the App ID Uri.
Now I would like to consume this service using Excel PowerQuery, authenticating using an account from the AzureAD. However, when I choose "Organizational account" and try to "Sign in", I get the following error:
Unable to connect. This credential type is not supported for this resource.
In Fiddler I can see that the request is made with a Bearer header, but it is empty.
I would like to achieve a behavior similar to when querying AzureAD Graph.
For example, if I try to consume https://graph.windows.net/.onmicrosoft.com/users?api-version=2013-04-05, a single sign-on window opens, and in Fiddler I can see that a token is passed.
How can I achieve this behavior? what am I missing?
Thanks!
Here is the expected flow between PowerQuery and an OData service during authentication:
When you enter the URI to your service in the builder, click ok, you will get a credential prompt asking for your credentials to access the service.
Typically, you would choose Organizational Account if Azure Active Directory (AAD) is your Identity Provider.
When you click sign in, PowerQuery will send a challenge request to your service, which is the empty bearer you are seeing. The reason is, we don't know what's your identity provider or where should we log you in, the request is expecting a 401/403 response with a WWW-Authenticate header that has the authentication endpoint url.
Here is the expected header format:WWW-Authenticate authorization_uri=”token service uri” quotes are optional. If we don't find that header, you get the error message 'Unable to connect. This credential type is not supported'.
For example, in your scenario, the token service uri is https://login.windows.net
When we receive that response, we will get the url from the header and issue a login request, at which point you will see the login page from AAD and you will be able to login using your organizational credentials.
We will wait for the sign in result, which should be a token, that token will be used to fill in the bearer in the header at anytime you request data from that service.
There are two important things regarding your application object in AAD to make this work:
The AppIdUris property has to have a wildcard URI that would match with your service URI. When we send the login request we have to include a resource id, the resource is the authority of the service we are connecting to. So if your service url is: myservice.com/myODatafeed.svc, the authority includes the scheme, the host and the port number, myservice.com/ would be the authority. For services that might have different tenants for example: company1.myservice.com, the AppIdUri has to have https://*.myservice.com. Otherwise, just https://myservice.com.
The second thing (and this on is AAD specific), AAD doesn't support first party client (PowerQuery) to third party service (your service) authentication today. but hopefully soon enough :) Maybe just when you get the rest done :)!
Update: This has been enabled in the latest release of PowerQuery. Your app will need to expose the user_imperonation scope, and you are good to go :)!