Google OAuth2 from .NET Web API - asp.net

First of all I would like to say that there is a chance that this has already been answered some place else, but please read my question carefully before coming to that conclusion, because I have seen so many samples now that do things in a different way or is in another language using components I can't easily interpret into something I know.
What I'm trying to accomplish is to make an ASP.NET Web API, that can authorize with Google aka get an access_token for further communication with Google REST APIs.
That is all I want it to be able to do - the later comminucation with the Google APIs is out of the scope of my Web API - it's just the access_token (either through user consent or by refresh token if user has already previously consented).
I have tried using several different approaches including using the Google API Client Library for .NET.
I'm not going to post code at this point since I'm pretty much confused by now as to which approach should be used in this scenario.
My latest read was this: https://developers.google.com/identity/protocols/OAuth2WebServer and I really wish they had added some C# samples here and not only PHP, Python and Ruby.
For what I'm trying to do, I'm not sure which type of credentials I should be using Oauth or Service Account and if OAuth should it then be for application type Web Application or Other?
It seems to make a great difference which I pick, but I haven't been able to find a guide that explains this so that there isn't any doubt which to use.
What I do know though is, that some of the things I have tried worked from my local computer, but didn't as soon as it was published to an IIS.
So if you can explain to me which of the many approaches is the right one for my scenario, it would be much appreciated - any code samples are also much welcome.
Summing Up:
I want to make an ASP.NET Web API, that can get an access token from Google (an access token to communicate with Google APIs) nothing else.
It has to me able to get an access token via the refresh token, so that the owner of the Google account, only has to grant access once.
Any further communication with Google APIs will happen through normal REST calls and is outside scope of my Web API.

I had the same problem and went down most of the same roads you went down, but having some experience writing OAuth implementations it wasn't too terribly difficult. Maybe you can use what I did, which seems to work for me. You will need to install RestSharp or use some other HttpClient.
First I wrote a GoogleAuthService that handles a few basic issues. Gets the authorization url, exchanges an authorization_code for an access_token and will refresh an access_token with a refresh_token.
GoogleAuthService
public class GoogleAuthService : IGoogleAuthService
{
/// <summary>
/// Step 1 in authorization process
/// </summary>
/// <param name="appId"></param>
/// <returns></returns>
public dynamic AuthorizationUrl(string appId)
{
var qs = HttpUtility.ParseQueryString("");
qs.Add("client_id", CloudConfigurationManager.GetSetting("ga:clientId"));
qs.Add("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"));
qs.Add("scope", CloudConfigurationManager.GetSetting("ga:scopes"));
qs.Add("access_type", "offline");
qs.Add("state", $"appid={appId}");
qs.Add("response_type", "code");
return new { Url = $"{CloudConfigurationManager.GetSetting("ga:authUrl")}?{qs.ToString()}" };
}
/// <summary>
/// Take the code that came back from Google and exchange it for an access_token
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<GoogleAccessTokenResponse> AccessToken(string code)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("code", code, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("redirect_uri", CloudConfigurationManager.GetSetting("ga:redirectUri"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "authorization_code", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleAccessTokenResponse>(request, Method.POST);
return response.Data;
}
/// <summary>
/// Take an offline refresh_token and get a new acceses_token
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<GoogleRefreshTokenResponse> RefreshToken(string refreshToken)
{
var client = new RestClient(CloudConfigurationManager.GetSetting("ga:tokenUrl"));
var request = new RestRequest();
request.AddParameter("refresh_token", refreshToken, ParameterType.GetOrPost);
request.AddParameter("client_id", CloudConfigurationManager.GetSetting("ga:clientId"), ParameterType.GetOrPost);
request.AddParameter("client_secret", CloudConfigurationManager.GetSetting("ga:clientSecret"), ParameterType.GetOrPost);
request.AddParameter("grant_type", "refresh_token", ParameterType.GetOrPost);
var response = await client.ExecuteTaskAsync<GoogleRefreshTokenResponse>(request, Method.POST);
return response.Data;
}
}
GoogleAccessTokenResponse
public class GoogleAccessTokenResponse
{
/// <summary>
/// Initial token used to gain access
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Use to get new token
/// </summary>
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
/// <summary>
/// Measured in seconds
/// </summary>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// Should always be "Bearer"
/// </summary>
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
GoogleRefreshTokenResponse
public class GoogleRefreshTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
Lastly you will need a Callback handler to accept the authorization_code.
GoogleOAuthController
public class GoogleOAuthController : Controller
{
private readonly ITenantGoogleAuthenticationService service;
private readonly ITenantService tenantService;
private readonly IGoogleAuthService googleAuthService;
public GoogleOAuthController(ITenantGoogleAuthenticationService service, ITenantService tenantService, IGoogleAuthService googleAuthService)
{
this.service = service;
this.tenantService = tenantService;
this.googleAuthService = googleAuthService;
}
public async Task<ActionResult> Callback(GoogleAuthResponse model)
{
try
{
var response = await this.googleAuthService.AccessToken(model.Code);
var qs = HttpUtility.ParseQueryString(model.State);
var appid = qs["appid"];
var tenant = await this.tenantService.TenantByAppId(appid);
var webTenant = await this.tenantService.GetWebTenant(appid);
var result = await this.service.GoogleAuthenticationSave(new TenantGoogleAuthenticationViewModel
{
AccessToken = response.AccessToken,
Expires = DateTime.Now.AddSeconds(response.ExpiresIn),
RefreshToken = response.RefreshToken,
TenantId = tenant.Id
}, webTenant);
return new RedirectResult("/");
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
}
Model for what is sent to you in the Callback
GoogleAuthResponse
public class GoogleAuthResponse
{
public string State { get; set; }
public string Code { get; set; }
public string Scope { get; set; }
}
Don't worry about the Tenant code as that is specific to my system and shouldn't have any bearing on this implementation. I use the "appid" to identify the users of my application and google is nice enough to allow me to pass that to them in the AuthorizationUrl and they nicely pass it back to me.
So basically you make a call to the GoogleAuthService.AuthorizationUrl() to get the URL. Redirect the User to that URL. Make sure you setup a ga:scopes in your Web.config. When the user agrees to all your security requests they will be forwarded back to the GoogleOAuthController and hit the Callback action, where you will take the code and exchange it for an access_token. At this point you can do like I do and just save it to your database so you can use it later. Looks like by default it expires in like an hour, so you will most likely be calling a RefreshToken before every use but that is up to you.

Related

"The read session is not available for the input session token." exception

I'm having a problem on Azure DocumentDB with a single partion collection.
Whenever I try to programmatically insert or query any document, I get an exception with the message saying
"The read session is not available for the input session token."
As this collection was newly created, I thought this was a generic error and I tried to recreate the collection on another database, but then when trying to create the collection I can't submit the deploy because I get asked of the partition key.
error
Standing on what the documentation says,
"You do not have to specify a partition key for these collections."
Can someone help? Am I doing something wrong?
The region is West Europe (in case it helps)
For the error you're getting about the input session token, can you add your code here?
For the issue in the portal where you're trying to create a collection, do the following:
In the partition key box, enter space and then press delete, you should get a green check mark in the box.
This will be fixed in the portal shortly.
I assume from your code that you are trying to create a generic pagination logic. From my experience with DocDB, pagination needs to be achieved by using the Continuation Token.
I generally have an extension that obtains said token and then I use it on subsequent requests like so:
/// <summary>
/// Paged results with continuation token
/// </summary>
/// <typeparam name="T"></typeparam>
public class PagedResults<T>
{
public PagedResults()
{
Results = new List<T>();
}
/// <summary>
/// Continuation Token for DocumentDB
/// </summary>
public string ContinuationToken { get; set; }
/// <summary>
/// Results
/// </summary>
public List<T> Results { get; set; }
}
/// <summary>
/// Creates a pagination wrapper with Continuation Token support
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static async Task<PagedResults<T>> ToPagedResults<T>(this IQueryable<T> source)
{
var documentQuery = source.AsDocumentQuery();
var results = new PagedResults<T>();
try
{
var queryResult = await documentQuery.ExecuteNextAsync<T>();
if (!queryResult.Any())
{
return results;
}
results.ContinuationToken = queryResult.ResponseContinuation;
results.Results.AddRange(queryResult);
}
catch
{
//documentQuery.ExecuteNextAsync might throw an Exception if there are no results
return results;
}
return results;
}
You can use this helper in your code along with the FeedOptions:
var feedOptions = new FeedOptions() { MaxItemCount = sizeOfPage };
var collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId);
PagedResults<T> results = await client.CreateDocumentQuery<T>(collectionUri,feedOptions).Where(predicate).ToPagedResults();
//You can check of the ContinuationToken and use it on another query
if(!string.IsNullOrEmpty(results.ContinuationToken)){
feedOptions.RequestContinuation = results.ContinuationToken;
PagedResults<T> moreResults = await client.CreateDocumentQuery<T>( collectionUri,feedOptions ).Where(predicate).ToPagedResults();
}
Also, I maintain a repo on Github that contains helpers and providers for DocDB that you are free to use if you want, most are based on the Performance guidelines article and personal experience.
Another word of advice, try to update your SDK to the latest version, either the .Net Full framework or the .Net Core version (depending on your project).

MVC6 and Web Api Authentication

Well, this was a mistake.
I decided to migrate my MVC5 application to MVC6 and things were going fine until I needed to migrate my authentication.
My MVC application was logging in using an external Web Api 2 application which returns a token.
I built a filter to handle that very simply like this:
/// <summary>
/// Uses the session to authorize a user
/// </summary>
public class SimpleAuthorize : AuthorizeAttribute
{
/// <summary>
/// Authorizes the user
/// </summary>
/// <param name="httpContext">The HTTP Context</param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var accessToken = httpContext.Session["AccessToken"];
if (accessToken == null)
return false;
return true;
}
}
which was applied to all controllers.
Now, it appears that you can't do that anymore as mentioned here.
So, how can I get my application to work with the API?
I have tried searching and found nothing that helps me with my situation. Does anyone know how I can solve this or could point me in the direction of some decent documentation?
You'd approach it by writing Authorization middleware which creates an identity out of the access token. Having a valid identity is enough for authorization to succeed, and then you can delve into policy should you need something more detailed. Something like
public class SessionAuthenticationHandler :
AuthenticationHandler<SessionAuthenticationOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var sessionToken = Request.HttpContext.Session.GetString("Token");
if (sessionToken == null)
{
return AuthenticateResult.Failed("No session token");
}
// Construct principal here
var principal =
new ClaimsPrincipal(new ClaimsIdentity(new[] {
new Claim("SessionToken", sessionToken) }, Options.AuthenticationScheme));
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
Options.AuthenticationScheme);
return AuthenticateResult.Success(ticket);
}
}
Having said that the reason ASP.NET has never used session to hold authentication details is due to session fixation attacks.

Getting User Id in Web Api handler when using Cachecow

I have a MVC Web Api project and am logging all requests and responses using a MessageHandler. When an api request comes in, the bearer token in the header lets Asp.Net do its thing and authenticates that user. The message handler therefore knows who the user is and we write that to a log file.
Now, to speed up things I'm caching with Cachecow. So I've added the cachecow handler after the MessageHandler and when a second request comes in, from a caching point of view everything works fine. The controller code is never hit and the response is returned from the cache.
However, the MessageHandler does not have a value for the User.Identity so I cannot tell who made the request.
I need to log all requests and identify who made them even when the code in the controllers is not hit.
I think one workaround is to force the api requests to pass the bearer token and user id in the header. That way I can check the user id claim and use that to log who made the request.
protected override async Task OutgoingMessageAsync(string correlationId, string requestInfo, byte[] message, string responseTimeMilliseconds)
{
await Task.Run(() =>
Debug.WriteLine(string.Format("{0} - Response: {1}\r\n{2}", correlationId, requestInfo, Encoding.UTF8.GetString(message))));
);
}
User identity is null when getting response from cache.
?HttpContext.Current.User.Identity
{System.Security.Claims.ClaimsIdentity}
[System.Security.Claims.ClaimsIdentity]: {System.Security.Claims.ClaimsIdentity}
AuthenticationType: null
IsAuthenticated: false
Name: null
Any ideas?
In authentication process, set object:
System.Threading.Thread.CurrentPrincipal = YourUserInformationObject;
This object need implement "System.Security.Principal.IPrincipal" Example
public class YourUserInformation : IPrincipal
{
public Int32 Id { get; set; }
public String NameUser { get; set; }
public IIdentity Identity { get; private set; }
public YourUserInformation()
{
this.Identity = new GenericIdentity(NameUser ?? "");
}
public bool IsInRole(string role) { return false; }
}
In authentication process you save object in System.Threading.Thread.CurrentPrincipal
public void Authentication(AuthorizationContext filterContext)
{
YourUserInformation user = YourMethodGetUserLogin();
System.Threading.Thread.CurrentPrincipal = user ;
}
Well you should create HttpContext from Request and there you will be able to use User.Identity object:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var context = ((HttpContextBase)request.Properties["MS_HttpContext"]);
var uname = username = context.User.Identity.Name;
var response = await base.SendAsync(request, cancellationToken);
return response;
}
Also check this article: http://arcware.net/logging-web-api-requests/
Hoope this help!
try get in
System.Threading.Thread.CurrentPrincipal

Server side caching and client side caching in webapi

I have to implement caching in asp.net web api methods because i am accessing data from third party datasource and Calling the third party data source is costly and data only gets updated every 24 hours.So with the help of Strathweb.I have implemented caching like this
/// <summary>
/// Returns the sorted list of movies
/// </summary>
/// <returns>Collection of Movies</returns>
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan =86400)]
public IEnumerable<Movie> Get()
{
return repository.GetMovies().OrderBy(c => c.MovieId);
}
/// <summary>
/// Returns a movie
/// </summary>
/// <param name="movie">movieId</param>
/// <returns>Movie</returns>
[CacheOutput(ClientTimeSpan = 86400, ServerTimeSpan = 86400)]
public Movie Get(int movieId)
{
var movie = repository.GetMovieById(movieId);
if (movie == null)
{
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No movie with ID = {0}", movieId)),
ReasonPhrase = "Movie ID Not Found"
};
throw new HttpResponseException(httpResponseMessage);
}
return movie;
}
but in Strathweb,i have seen two attributes one is ClientTimeSpan and Other is ServerTimeSpan.I am not sure when to use ClientTimeSpan and when to use ServerTimeSpan.In the simplest term i want to understand when to use serverside caching and when to use clientside caching and what are the diffrences between both.
As the definition says
ClientTimeSpan (corresponds to CacheControl MaxAge HTTP header)
ServerTimeSpan (time how long the response should be cached on the server side)
Code sample , with explanation.
//Cache for 100s on the server, inform the client that response is valid for 100s
[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100)]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
//Inform the client that response is valid for 50s. Force client to revalidate.
[CacheOutput(ClientTimeSpan = 50, MustRevalidate = true)]
public string Get(int id)
{
return "value";
}
SOURCE
ClientTimeSpan
Use client side caching if you want to allow clients (usually browsers) to cache your data locally on the users computer. The benefits are that the clients may not requests your API until the cache expires. On the other side you can't invalidate this cache because it is stored on the client side. Use this cache for non-dynamic/not frequently changing data.
ServerTimeSpan
Stores data on your server. You can easily invalidate this cache but it needs some resources (memory)

Can ServiceStack routes not handle special characters in their values?

I'm developing an API for our business.
Requests should require authTokens that require a POST http verb to retrieve. The flow should work like this-
User's client POSTS username and password (ssl protected) to the GetAuthToken service. Service returns auth token.
User's client can use token in any other request, no longer requiring POST.
I've written a convenience rest function, CheckAuthToken, to allow users to debug whether their working auth token is correct. It requires the email address of the user and the auth token to check. This works fine in SOAP and via POST, but the route doesn't seem to work via GET.
Here's the DTO:
[Route("/auth/CheckAuthToken/{EmailAddress}/{AuthToken}")]
public class CheckAuthToken
{
public string EmailAddress { get; set; }
public string AuthToken { get; set; }
}
If I request, for example (note the %2E, at the recommendation of this post: ServiceStack Handler Not Found When Periods Present in Path):
GET /auth/CheckAuthToken/aaron%40meemailsite%2Ecom/Hnot0real0auth0token4mzSBhKwFXY6xQcgX6XqsE%3D HTTP/1.1\r\n
I still get a 404. It seems like the period is being decoded before handing off to ServiceStack.
I recognize that I could use query string variables instead, making my request this:
GET /auth/CheckAuthToken?EmailAddress=aaronb%40bluebookinc%2Ecom&AuthToken=HwjQoKiHD2HSngFeeCH1k4mzSBhKwFXY6xQcgX6XqsE%3D HTTP/1.1\r\n
But, I was hoping to be more flexible than that, especially in my business layer REST services, which will also require user identification via email on certain functions.
Any ideas?
What version of ServiceStack are you running?
This is a passing test in the latest version (3.9.55). I also tested with a simple API with your endpoints and was having no problems passing those values in the url.
[Route("/auth/CheckAuthToken/{EmailAddress}/{AuthToken}")]
public class CheckAuthToken : IReturn
{
public string EmailAddress { get; set; }
public string AuthToken { get; set; }
}
[Test]
public void Test()
{
var url = new CheckAuthToken() {
EmailAddress = "me#test.com",
AuthToken = "fake"
}.ToUrl("GET");
Assert.That(url, Is.EqualTo("/auth/CheckAuthToken/me%40test.com/fake"));
}

Resources