Oauth2 and Access Token in SharePoint App - oauth-2.0

I am creating an intranet on SharePoint - O365 where I can a widget where I need to pull calendar events and display them for a week. Here is a steps walk through:
a. User log in to Intranet
b. Access token is generated to access Office 365 REST API
c. Calendar events are fetched and displayed.
Here is my problem:
I thought of 2 options to generate the access token
option a: Create a WCF application which accpets user context and generate the token. This will fetch the results and update a list. My intranet app can read a calendar list and update the widget. This didnt work since I was not able to pass the user context from SP to WCF method so that access token can be generated.
Option b: Use the following code (which I have done as of now) but it display the access token in URL which is not good for the client.
var clientId = '>>sample>>';
var replyUrl = '<<>>';
var endpointUrl = 'https://outlook.office365.com/api/v1.0/me/events';
var resource = "https://outlook.office365.com/";
var authServer = 'https://login.windows.net/common/oauth2/authorize?';
var responseType = 'token';
var url = authServer +
"response_type=" + encodeURI(responseType) + "&" +
"client_id=" + encodeURI(clientId) + "&" +
"resource=" + encodeURI(resource) + "&" +
"redirect_uri=" + encodeURI(replyUrl);
window.location = url;
So is there any other way to achieve this??
Ankush

Since you mentioned that you want to use the WCF, are you developing an provided host SharePoint app?
If I understand correctly, we can use the Explicit Authorization Code Grant Flow which didn’t expose the Access token to the user agent. The following diagram illustrates the Authorization Code Grant flow:
And here is the core code to retrieve the access token for the Office 365 resource for you reference:
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
string token= authResult.AccessToken;
return authResult.AccessToken;
});
var dcr = await discClient.DiscoverCapabilityAsync(capabilityName);
return new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
}
The full code sample you can refer to here. And here is a helpful link that discuss the difference between explicit and implicate authentication flow.

Related

Mailkit Can't authenticate with O365 oAuth2 account

I tried to authenticate on a O365 application I created on the Azure portal and it doesn't work as expected.
The following code works well but it's using a login/password and it's not recommended by Microsoft. (found here https://github.com/jstedfast/MailKit/issues/989)
var scopes = new[] { "https://outlook.office365.com/IMAP.AccessAsUser.All" };
var confidentialClientApplication = PublicClientApplicationBuilder.Create(_clientId).WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs).Build();
SecureString securePassword = new NetworkCredential("", _userPassword).SecurePassword;
var acquireTokenByUsernamePasswordParameterBuilder = confidentialClientApplication.AcquireTokenByUsernamePassword(scopes, _userMail, securePassword);
var authenticationResult = acquireTokenByUsernamePasswordParameterBuilder.ExecuteAsync().Result;
if (_debugCall)
{
imapClient = new ImapClient(new ProtocolLogger(_configurationId + "_IMAP_" + DateTime.Now.ToString("yyyyMMddHHssffff") + ".log"));
}
else
{
imapClient = new ImapClient();
}
imapClient.CheckCertificateRevocation = false;
imapClient.ServerCertificateValidationCallback = (s, c, h, e) => true;
imapClient.Connect(_webServiceUrl, _webServicePort, SecureSocketOptions.Auto);
imapClient.Authenticate(new SaslMechanismOAuth2(_userMail, authenticationResult.AccessToken));
if (string.IsNullOrEmpty(_folder))
{
oFolder = imapClient.Inbox;
}
else
{
oFolder = imapClient.GetFolder(_folder);
}
oFolder.Open(FolderAccess.ReadWrite);
In fact I want to be able to authenticate using the tenanid, client secret and clientid but without the interactive mode (as the app is a windows services).
So I tried to use another code with the tenantid, clientSecret and ClientId but I receive the "Authentication failed" error message :
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(_clientId)
.WithClientSecret(_clientSecret)
.WithRedirectUri("http://localhost")
.WithAuthority(new Uri("https://login.microsoftonline.com/" + _tenantid + "/"))
.Build();
var scopes = new[] { "https://outlook.office365.com/.default" };
var authenticationResult = confidentialClientApplication.AcquireTokenForClient(scopes);
var authToken = authenticationResult.ExecuteAsync().Result;
var oauth2 = new SaslMechanismOAuth2(_userMail, authToken.AccessToken);
imapClient = new ImapClient(new ProtocolLogger("TEST_IMAP_" + DateTime.Now.ToString("yyyyMMddHHssffff") + ".log"));
imapClient.CheckCertificateRevocation = false;
imapClient.ServerCertificateValidationCallback = (s, c, h, e) => true;
imapClient.Connect(_webServiceUrl, _webServicePort, SecureSocketOptions.Auto);
imapClient.Authenticate(oauth2);
I've the following permission for my app on the Azure portal:
MSGraph
IMAP.AccessAsUser.All
Mail.Read
Mail.ReadWrite
Mail.Send
Did I miss something? I'm afraid it may be impossible? The official sample on Mailkit website use the interactive mode.
Btw, I'm using Mailkit v2.4
Thank you for your help.
It appears that OAUTH2 authentication with Office365 via the non-interactive method is unsupported by the Microsoft Exchange IMAP/POP3/SMTP protocols and that the only way to get access to Office365 mail using the non-interactive method of OAUTH2 authentication is via the Microsoft.Graph API.
I've been getting a lot of questions about this over the past few months and as far as I'm aware, no one (myself included) has been able to find a way to make this work.
I keep hoping to see someone trying to do this (even in another language) here on StackOverflow with an accepted answer. So far, all I've seen are questions about OAuth2 using the interactive approach (which, as you've seen, I have written documentation for and is known to work well with MailKit).

using Microsoft graph API i want user profile photo

Hi I am trying to get user photo, used
var tenantId = configuration.GetSection("AzureAd").GetSection("TenantId").Value;
var clientId = configuration.GetSection("AzureAd").GetSection("ClientId").Value;
var clientSecret = configuration.GetSection("AzureAd").GetSection("clientSecret").Value;
var InviteRedirectUrl = configuration.GetSection("AzureAd").GetSection("InviteRedirectUrl").Value;
var Instance = configuration.GetSection("AzureAd").GetSection("Instance").Value;
var URL = Instance + tenantId + "/v2.0";
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(URL)
.WithClientSecret(clientSecret)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
Stream photo = await graphServiceClient.Me.Photo.Content.Request().GetAsync();
I got following error
Code: BadRequest Message: Current authenticated context is not valid for this request. This occurs when a request is made to an endpoint that requires user sign-in. For example, /me requires a signed-in user. Acquire a token on behalf of a user to make requests to these endpoints. Use the OAuth 2.0 authorization code flow for mobile and native apps and the OAuth 2.0 implicit flow for single-page web apps. Inner error:
how to solve it?

Set url for GraphApi B2C login

I need to query the Graph API to get the username in the claims.
I've implemented something based on what I've found on the net, but I keep getting 403 Forbidden, from Graph API.
Can anyone help me with this?
This is my code:
var clientId = "clientId";
var clientSecret = "clienSecret";
var tenant = "tenantName";
var userObjectId = claimsPrincipal.Claims.Where(i => i.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault().Value;
var aadGraphVersion = "api-version=1.6";
var query = "/users/" + userObjectId;
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
// The ClientCredential is where you pass in your client_id and client_secret, which are
// provided to Azure AD in order to receive an access_token using the app's identity.
ClientCredential credential = new ClientCredential(clientId, clientSecret);
// First, use ADAL to acquire a token using the app's identity (the credential)
// The first parameter is the resource we want an access_token for; in this case, the Graph API.
AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.windows.net", credential);
// For B2C user management, be sure to use the Azure AD Graph API for now.
HttpClient http = new HttpClient();
//var url = "https://graph.windows.net/" + tenant + "/users/" + userObjectId + "/?api-version=1.6";
//var url = graphResource + "tenant" + "/users/" + userObjectId + "/?api-version=1.6";
string url = "https://graph.windows.net/" + tenant + "/users/" + userObjectId + "?" + aadGraphVersion;
//url += "&" + query;
// Append the access token for the Graph API to the Authorization header of the request, using the Bearer scheme.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
I think I have a problem with the URL that is not set correctly. The token is correct, I got it ok with the credentials.
I do think it is an issue with the URL. You are getting this error as you have provided user read permissions to your registered application. Please make sure that -
You go to Application registrations menu on your tenant
Select "Required Permissions" menu and click on Windows Azure Active Directory
In the "Enable Access" menu select "Read Directory Data" permissions under Application Permissions section and click save.
Once saved on "Required Permissions" menu click on "Grant Permissions" button to provide the consent.
You may need to select other options like "Read and Write Directory Data" if you wish to provide your application to create/update/delete users.

How to get an ACS app-only access token for Project Online

I'm trying to get an AppOnly access token for use in the Authorization Bearer header of my request to a REST endpoint in Project Online (SharePoint). Following is a snippet of the code that I was using to retrieve the access token.
private OAuth2AccessTokenResponse GetAccessTokenResponse()
{
var realm = TokenHelper.GetRealmFromTargetUrl([[our_site_url]]);
var resource = $"00000003-0000-0ff1-ce00-000000000000/[[our_site_authority]]#{realm}";
var formattedClientId = $"{ClientId}#{realm}";
var oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithClientCredentials(
formattedClientId,
ClientSecret,
resource);
oauth2Request.Resource = resource;
try
{
var client = new OAuth2S2SClient();
var stsUrl = TokenHelper.AcsMetadataParser.GetStsUrl(realm);
var response = client.Issue(stsUrl, oauth2Request) as OAuth2AccessTokenResponse;
var accessToken = response.AccessToken;
}
catch (WebException wex)
{
using (var sr = new StreamReader(wex.Response.GetResponseStream()))
{
var responseText = sr.ReadToEnd();
throw new WebException(wex.Message + " - " + responseText, wex);
}
}
}
I keep getting 403 Forbidden as the response from the server, even if I include site collection admin credentials with my request. Does anyone out there have any ideas?
After creating a support ticket with Microsoft to figure this out we eventually decided to move away from using app permissions for console application authorization.
Our workaround was to create SharePointOnlineCredentials object using a service account, and then get the Auth cookie from the credentials object to pass with our WebRequest. This solution came from scripts found here: https://github.com/OfficeDev/Project-REST-Basic-Operations

Zend Gdata Youtube and auto login

Hello guys I need help in auto login to youtube.com to upload videos "browser-based" (and later get them data to show in a site by api). So basicly I downloaded extension from here http://framework.zend.com/downloads/latest Zend Gdata. And make it work.
It works fine (demos/.../YouTubeVideoApp). But how can i do auto login to youtube without confirmation page ("grant access" \ "deny access")? Currently I use developer key to work with youtube api.
The message of confirmation is
An anonymous application is requesting access to your Google Account for the product(s) listed below.
YouTube
If you grant access, you can revoke access at any time under 'My Account'. The anonymous application will not have access to your password or any other personal information from your Google Account. Learn more
This website has not registered with Google to establish a secure connection for authorization requests. We recommend that you continue the process only if you trust the following destination:
http://somedomain/operations.php
In general I need create connection to youtube (by api) and upload there (using my own account) video without any popups and confirmation pages.
i think all you need is to get a access token and set it to a session value "$_SESSION['sessionToken']". Combination of javascript and PHP will need to do this. previously i always have to grant access or deny it while using Picasa web API but after changes that i described below, grant or access page is no longer needed.
I have not integrated youtube with zend Gdata but have integrated Picasa web Albums using it
make a login using javascript popup and get a token for a needed scope. below is a javascript code. change your scope to youtube data as in this scope for picasa is used.. click function "picasa" on your button onclick.
var OAUTHURL = 'https://accounts.google.com/o/oauth2/auth?';
var VALIDURL = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=';
var SCOPE = 'https://picasaweb.google.com/data';
var CLIENTID = YOUR_CLIENT_ID;
var REDIRECT = 'http://localhost/YOUR_REDIRECT_URL'
var LOGOUT = 'http://accounts.google.com/Logout';
var TYPE = 'token';
var _url = OAUTHURL + 'scope=' + SCOPE + '&client_id=' + CLIENTID + '&redirect_uri=' + REDIRECT + '&response_type=' + TYPE;
var acToken;
var tokenType;
var expiresIn;
var user;
var loggedIn = false;
function picasa() {
var win = window.open(_url, "windowname1", 'width=800, height=600');
var pollTimer = window.setInterval(function() {
console.log(win);
console.log(win.document);
console.log(win.document.URL);
if (win.document.URL.indexOf(REDIRECT) != -1) {
window.clearInterval(pollTimer);
var url = win.document.URL;
acToken = gup(url, 'access_token');
tokenType = gup(url, 'token_type');
expiresIn = gup(url, 'expires_in');
win.close();
validateToken(acToken);
}
}, 500);
}
function validateToken(token) {
$.ajax({
url: VALIDURL + token,
data: null,
success: function(responseText){
//alert(responseText.toSource());
getPicasaAlbums(token);
loggedIn = true;
},
dataType: "jsonp"
});
}
function getPicasaAlbums(token) {
$.ajax({
url: site_url+"ajaxs/getAlbums/picasa/"+token,
data: null,
success: function(response) {
alert("success");
}
});
}
//credits: http://www.netlobo.com/url_query_string_javascript.html
function gup(url, name) {
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\#&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( url );
if( results == null )
return "";
else
return results[1];
}
Here i am making a ajax call in function "getPicasaAlbums" and setting token to a $_session there and after it i am able to get a album listing using zend queries. here is a some code of php file that i am calling using ajax in function "getPicasaAlbums".
function getAlbums($imported_from = '',$token = '') {
//echo $imported_from; //picasa
//echo $token;
$_SESSION['sessionToken'] = $token;// set sessionToken
$client = getAuthSubHttpClient();
$user = "default";
$photos = new Zend_Gdata_Photos($client);
$query = new Zend_Gdata_Photos_UserQuery();
$query->setUser($user);
$userFeed = $photos->getUserFeed(null, $query);
echo "<pre>";print_r($userFeed);echo "</pre>";exit;
}
i think this will help you a little in your task. relpace above "getAlbums" function's code with your youtube zend data code to retrieve data.
good example & referene of a login popup is here
http://www.gethugames.in/blog/2012/04/authentication-and-authorization-for-google-apis-in-javascript-popup-window-tutorial.html

Resources