Incremental Google OAuth with Asp.Net Owin Oauth - asp.net

I'm looking for a solution to doing incremental authorization against Google's api's with Asp.Net's Owin OAuth Libraries.
I know how to set scope for specific api's, but I would like to do it incrementally and can only see how to set it on globally.
Doc on Google Oauth Incremental Auth...
https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth
Current VB Code...
Public Sub ConfigureAuth(app As IAppBuilder)
Dim googleCreds = New GoogleOAuth2AuthenticationOptions() With {
.ClientId = "xxxx",
.ClientSecret = "xxx"
}
googleCreds.Scope.Add("https://www.googleapis.com/auth/analytics.readonly")
app.UseGoogleAuthentication(googleCreds)
' Would like to add another way to specify GoogleDrive, YouTube, Google+ scopes
' Example code that doesn't work that would add a 2nd Google Oauth Listener
googleCreds.Scope.Clear()
googleCreds.Scope.Add("https://www.googleapis.com/auth/drive.file")
googleCreds.AuthenticationType = "GoogleDrive"
app.UseGoogleAuthentication(googleCreds)
End Class

Here is the solution I came up with. It involves passing a "scope" parameter in the url and then parsing that in the "OnApplyRedirect" function of the Authentication options and then manually injecting the correct scope url into the redirect url.
Dim googleCreds = New GoogleOAuth2AuthenticationOptions() With {
.ClientId = "xxx",
.ClientSecret = "xxx",
.Provider = New Microsoft.Owin.Security.Google.GoogleOAuth2AuthenticationProvider() With { _
.OnApplyRedirect = Function(context)
Dim queryString = HttpContext.Current.Request.QueryString.ToString()
Dim queryParms = HttpUtility.ParseQueryString(queryString)
' Change the value of "redirect" here
' e.g. append access_type=offline
Dim redirect As String = context.RedirectUri
redirect += "&access_type=offline"
redirect += "&approval_prompt=force"
redirect += "&include_granted_scopes=true"
Dim uri = New Uri(redirect)
If (Not String.IsNullOrEmpty(queryParms.Get("scope"))) Then
Dim scope = queryParms.Get("scope")
Dim redirectQueryString = HttpUtility.ParseQueryString(uri.Query)
Select Case scope
Case "Analytics"
redirectQueryString.Set("scope", "https://www.googleapis.com/auth/analytics.readonly")
Case "YoutTube"
redirectQueryString.Set("scope", "https://gdata.youtube.com")
Case "Drive"
redirectQueryString.Set("scope", "https://www.googleapis.com/auth/drive.file")
Case Else
LoggingUtility.LogErrorMessage("Invalid scope passed in: scope: " + scope)
End Select
redirect = uri.GetLeftPart(UriPartial.Path) + "?" + redirectQueryString.ToString()
End If
context.Response.Redirect(redirect)
End Function, _
}
}
'Google Analytics
app.UseGoogleAuthentication(googleCreds)

In OWIN version 3.1 incremental authentication works as expected. (It may well work in earlier versions, I've not tested that).
There were two tricks that were necessary to get this working:
OWIN expects a 401
If you try and call:
var ctx = HttpContext.Current.Request.GetOwinContext();
var properties = new AuthenticationProperties
{
RedirectUri = "/your/redirect",
};
ctx.Authentication.Challenge(properties, "Google");
While logged in to your application, the call to Google from the Middleware will not happen.
This can be confusing, since it just works, when using the exact same code during login.
The reason is that the Middleware only works when the current HTTP response is 401, so after the call above you need something like:
Page.Response.StatusCode = 401;
Page.Response.End();
Specification of scopes is picky
In my original code to try and call with a new scope, I had something like this:
var ctx = HttpContext.Current.Request.GetOwinContext();
var properties = new AuthenticationProperties
{
RedirectUri = redirect,
};
properties.Dictionary["scope"] ="https://www.googleapis.com/auth/calendar.readonly";
This was calling Google as expected and Goggle was correctly challenging based on the requested scopes, but then the response back to my server was breaking (never did find out where).
What I found I needed to do was to resend all my base scopes as well:
var scopes = new List<string>
{
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
};
if (additionalScopes != null)
{
scopes = scopes.Union(additionalScopes).ToList();
}
properties.Dictionary["scope"] = string.Join(" ", scopes);

Related

How to get Authorization_code and access_Token and send envelope DocuSign using asp.net

I am trying to send envelope using docuSign, but having error while get authorization_code which need to get access_Token.
Please help me to get authorization_Code and access_Token.
I am using ASP.Net web forms and .NET framework 4.5.2
DocuSign.eSign.dll 5.2.0
DocuSign.Integration.Client.dll 1.7.2
Request:
https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature&client_id=4f464e25-6425-4ea6-915b-aa9bac5b8ce7&redirect_uri=https://account-d.docusign.com/ds/login?authType=JWT
Response:
The redirect redirect_uri is not registered properly with DocuSign
string RedirectURI = "https://account-d.docusign.com/ds/login?authType=JWT";
string ClientSecret = "****";
string IntegratorKey = "****";
Uri oauthLoginUrl = GetAuthorizationUri(IntegratorKey, scopes, RedirectURI, OAuth.CODE, null);
WebRequest request = WebRequest.Create(oauthLoginUrl);
WebResponse response = request.GetResponse();
public Uri GetAuthorizationUri(string clientId, List<string> scopes, string redirectUri, string responseType, string state = null)
{
string formattedScopes = (scopes == null || scopes.Count < 1) ? "" : scopes[0];
StringBuilder scopesSb = new StringBuilder(formattedScopes);
for (int i = 1; i < scopes.Count; i++)
{
scopesSb.Append("%20" + scopes[i]);
}
UriBuilder builder = new UriBuilder("https://account-d.docusign.com")
{
Scheme = "https",
Path = "/oauth/auth",
Port = 443,
Query = BuildQueryString(clientId, scopesSb.ToString(), redirectUri, responseType, state)
};
return builder.Uri;
}
Please make sure that the specified redirect URI is configured under the redirect URI section for the integration key that is being used. Please keep in mind that the same exact redirect URI has to be used when using your authentication URL.
The specified section is found under Settings > Apps and Keys > Click on your integration key > Edit
I would also recommend creating a new integration key, since you have shared it publicly on this thread.

OneDrive for Business :"invalid_request","error_description":"AADSTS90014: The request body must contain the following parameter: 'grant_type

I'm trying to integrate the OneDrive for Busines to a Web Form App.
For this I use the documentation given at this url
In web Form App I have two Pages:
First one is Login page which have a button for login
On login button click I create a GET Request to OneDrive for Business API using the following code:
HttpClient client = new HttpClient();
Redirecturi = Uri.EscapeDataString(Redirecturi);
string url = string.Format("https://login.windows.net/common/oauth2/authorize?response_type=code&client_id={0}&redirect_uri={1}", ClienId, Redirecturi);
var response = client.GetAsync(url);
var json = response.Result.Content.ReadAsStringAsync();
Label2.Text = json.Result;
When I click the login button it takes me to micorosoft login service and sends me back to callback.aspx page with access code (Redirect URI configured on azure)
I got the access code.
On the second page I redeem the access code and make a POST request to get the Authentication token.
Here is the code for the second page:
private string BaseUri="https://login.windows.net/common/oauth2/token";
public string Redirecturi = "http://localhost:51642/CallBack.aspx";
public string ResourcesId = "https://api.office.com/discovery/";
private string ClienId = "180c6ac4-5829-468e-.....-822405804862"; ///truncated//azure
private string ClientSecert = "G4TAQzD8d7C4...OE6m366afv8XKbTCcyXr4=";//truncated
protected void Page_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(Request.QueryString[OAuthConstants.AccessToken]))
{
// There is a token available already. It should be the token flow. Ignore it.
return;
}
if (!string.IsNullOrEmpty(Request.QueryString[OAuthConstants.Code]))
{
string _accessCode = Request.QueryString[OAuthConstants.Code];
HttpClient client = new HttpClient();
// BaseUri = Uri.EscapeDataString(BaseUri);
Redirecturi = Uri.EscapeDataString(Redirecturi);
ResourcesId = Uri.EscapeDataString(ResourcesId);
string url = string.Format("{0}?client_id={1}&redirect_uri={2}&grant_type=authorization_code&client_secret={3}&code={4}&grant_type=authorization_code&resource={5}", BaseUri, ClienId, Redirecturi, ClientSecert, _accessCode, ResourcesId);
var response = client.PostAsync(url, null);
var json = response.Result.Content.ReadAsStringAsync();
Response.Write(json);
}
}
But instead of Response I am get the following error. Which say include the grant_type in url. I have already added (you can check in code).
I get same error the same error without including it.
Here is the error
{"error":"invalid_request","error_description":"AADSTS90014: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: 2adb3a7f-ceb1-4978-97c4-3dc2d3cc3ad4\r\nCorrelation ID: 29fb11a0-c602-4891-9299-b0b538d75b5f\r\nTimestamp: 2015-07-15 09:58:42Z","error_codes":[90014],"timestamp":"2015-07-15 09:58:42Z","trace_id":"2adb3a7f-ceb1-4978-97c4-3dc2d3cc3ad4","correlation_id":"29fb11a0-c602-4891-9299-b0b538d75b5f","submit_url":null,"context":null}
Please help to know where or what is getting wrong.
Any kind of help will be appreciable
You're adding the parameters to the request querystring. You have to post the data in the request body.
var content = new StringContent(
"grant_type=authorization_code" +
"&client_id=" + ClienId +
"&redirect_uri=" + Redirecturi +
"&client_secret=" + ClientSecert +
"&code=" + _accessCode +
"&resource=" + ResourcesId,
Encoding.UTF8,
"application/x-www-form-urlencoded");
var response = httpClient.PostAsync(BaseUri, content);
var result = response.Result.Content.ReadAsStringAsync();
use FormUrlEncodedContent instead of StringContent (form data post)
var formContent = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "client_id", clientId },
{ "client_secret", clientSecret },
{ "code", authCode },
{ "redirect_uri", redirectUri },
{ "grant_type", "authorization_code" }
});
var response = await httpClient.PostAsync("https://login.microsoftonline.com/common/oauth2/token", formContent);
Sharing for future readers because this error is not specific to OneDrive only but can arise in other Microsoft tools
I was getting this error when working with Microsoft Bot Framework's Skype bot. In my case the bot file the appId and appSecret was wrongly set to clientId and clientSecret
Changing the same to appId and appSecret fixed the issue.

Creating Google Drive DriveService with existing access token

I am using ASP.NET Web API and Google.Apis.Drive.v2 Client Library for .NET to upload files to users Drive.
All examples of using the Drive Client Library for .NET require a authentication flow. But how should I create the DriveService when I already know the access token?
Despite the fact that have been 2 years since the question has been asked, today I've encountered the same situation and my solution is:
var valid_token = "Pass_the_valid_token_here";
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var fakeflow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "fakeClientId",
ClientSecret = "fakeClientSecret"
}
});
UserCredential credential = new UserCredential(fakeflow, "fakeUserId", token);
var serviceInitializer = new BaseClientService.Initializer()
{
//ApplicationName = "Storage Sample",
HttpClientInitializer = credential
};
DriveService service = new DriveService(serviceInitializer);
Update
You could create your own custom token but the issue with this is going to be that the client library will not be able to refresh your access without the refresh token.
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var authorization = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "lientId",
ClientSecret = "ClientSecret"
}
});
var credential = new UserCredential(authorization, "user", token);
The issue you are going to have with this is that the client library is not going to be able refersh the access token after it has expired since you are not supplying a refresh token its only going to work for an hour.
The answer from Svetoslav Georgiev has so far worked well for me - Can't thank you enough. Google really don't help themselves with the lack of .Net (Asp Core) samples etc. Anway, one problem I did run into was that of referer restriction, so a addition/slight modification to the answer - Once you have the "service" and want to say upload a file, you need to set the referer on a buried HttpClient property...
FilesResource.CreateMediaUpload uploadRequest;
byte[] byteArray = Encoding.UTF8.GetBytes(html);
using (var stream = new MemoryStream(byteArray))
{
uploadRequest = service.Files.Create(fileMetadata, stream, "text/html");
uploadRequest.Service.HttpClient.DefaultRequestHeaders.Referrer = new Uri($"{baseUrl}");
uploadRequest.Fields = "id";
var progress = uploadRequest.Upload();
if (progress.Exception != null)
{
throw progress.Exception;
}
var file = uploadRequest.ResponseBody;
.... do what you will with file ....
}

How can i use an already authenticated session for google calendar?

I've implemented simple Google+ authentication on my MVC5 app and I'd like to access their google calendar. How do I do this using the MVC identity system and my already authenticated user?
Dim authGOps = New GooglePlusAuthenticationOptions() With {
.Caption = "Google+",
.ClientId = "MYCLIENTRID",
.ClientSecret = "MYCLIENTSECRET",
.Provider = New GooglePlusAuthenticationProvider() With {
.OnAuthenticated = Async Function(context)
context.Identity.AddClaim(New Claim(GooglePlusAccessTokenClaimType, context.AccessToken))
End Function
}
}
authGOps.Scope.Add("https://www.googleapis.com/auth/calendar")
app.UseGooglePlusAuthentication(authGOps)
Getting the calendar service:
Dim calendarService = New CalendarService(New Google.Apis.Services.BaseClientService.Initializer() With {
WHAT GOES HERE TO AUTHENTICATE USING MY OLD AUTH CEDENTIALS?
}
So I as well would love to use the Service as it's documented almost everywhere, but I found a workaround to at least getting the data and not having to login again.
Make sure to Nuget Json.Net to deserialize and strongly type. Otherwise you'll get a Json string to manage.
It's in C#, but I'm sure the translation won't be too difficult. Hope it helps!
var claimsIdentity = User.Identity as ClaimsIdentity;
var claims = claimsIdentity.Claims;
var accessTokenClaim = claims.FirstOrDefault(x => x.Type == GooglePlusAccessTokenClaimType);
if (accessTokenClaim != null)
{
string calendarUrl = "https://www.googleapis.com/calendar/v3/users/me/calendarList?access_token=" + Uri.EscapeDataString(accessTokenClaim.Value);
using(var client = new HttpClient())
{
var response = await client.GetAsync(calendarUrl);
var calendarList = JsonConvert.DeserializeObject<CalendarList>(await response.Content.ReadAsStringAsync());
}
}

Hammock Facebook graph API query parameter

I am using Hammock to get data from the Facebook graph API. When user logs into my web site I want to get all data from his Facebook profile since he last logged my web site. So when the user logs into my web site I update his login time and from his last login date to now, I take all his Facebook profile data. I am using Hammock to do this.
I want to give Hammock a creation date parameter but it doesn't work.
This works:
RestClient client = new RestClient { Authority = "https://graph.facebook.com/" };
RestRequest request = new RestRequest { Path = "/me/status " };
But this does not:
string query = "SELECT status_id, message FROM status WHERE status_id = me() and creationTime.......";
RestClient client = new RestClient { Authority = "https://graph.facebook.com/" };
RestRequest request = new RestRequest { Path = "/me?q=query" };
How can I give the creation time to the hammock request?
This code works for me:
RestClient client = new RestClient { Authority = "https://graph.facebook.com/" };
RestRequest request = new RestRequest { Path = "/me/statues" };
But it loads all user statuses. I want to give a datetime parameter; for example, if I want to get user statuses since 30.08.2012.
In your last example, you can add the since query string to your request:
... { Path = "/me/statuses?since=2012-08-30" };
For your second example, you're using the wrong URL to make a FQL query. Try this instead:
... { Path = "/fql?q=" + Server.UrlEncode(query) };

Resources