How can i use an already authenticated session for google calendar? - asp.net

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());
}
}

Related

How to consume my authorized web api to my mvc web app

I have a web api controller which retrieves data from my database and a mvc web app controller which consumes this web api.
In my web api I used Microsoft.Owin for security and generating a token. Later, I am using this token taken from my web api via postman, and placing it statically on my web app. What I want to do is dynamically store every token I generate from every request and not copy-pasting it every time.
I used this video for creating my web api and generatin jwt token, and this video to consume my web api. Please help me, I'm stuck here for days now.
EDIT:
My web api controller:
[Authorize]
[HttpGet]
public IHttpActionResult GetOperators()
{
IList<OperatorClass> OperatorObject = myEntity.Operator.Include("Operator").Select(x => new OperatorClass()
{ id = x.id,
name = x.name,
lastname = x.lastname,
mobile = x.mobile,
username = x.username,
password = x.password
}).ToList<OperatorClass>();
return Ok(OperatorObject);
}
Startup.cs
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
var myProvider = new MyAuthorizationServerProvider();
OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
Provider = myProvider
};
MyAuthorizationServerProvider.cs
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
if(context.UserName=="test" && context.Password == "test")
{
context.Validated(identity);
}
else
{
context.SetError("Invalid grant", "Provided username or password are incorrect");
return;
}
}
My web application controller
if (opc.username == "test" && opc.password == "test")
{
string token = "07Jv8mQ-pg6MlGdAAVJqxzsJ";
IEnumerable<OperatorClass> OperatorObject = null;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://localhost:44304/api/");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
var resultDisplay = await client.GetAsync("data");
if (resultDisplay.IsSuccessStatusCode)
{
var readTable = resultDisplay.Content.ReadAsAsync<IList<OperatorClass>>();
readTable.Wait();
OperatorObject = readTable.Result;
return View(OperatorObject);
}
else
{
OperatorObject = Enumerable.Empty<OperatorClass>();
ModelState.AddModelError(String.Empty, "No records found");
ViewBag.Error = "Token error. It may be incorrect or it has already expired. Check your token provider!";
return View("Error");
}
}
else
{
ViewBag.Error = "Incorrect username or password!";
return View("Login");
}
1- Why don't you store that token in database tables or Session for later use in request.
2- Everytime you ask question try to share relevant code as well next time.

Implement Microsoft Graph API in a .netcore API project

I am trying to write a .netcore API which gets a bearer token from third party Webapp. This .netcore API should access the Microsoft graph API and get the user group information back from Azure AD.
I was following the sample project https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore.
But unfortunately this uses AAD graph rather tha Microsoft graph API.
I tried to implement Graph API in the .netcore api project in the above sample.
Things I have tried
I have changed the AAD graph to Graph API in the AzureAdAuthenticationBuilderExtensions.cs(in the web app project)
options.Resource = "https://graph.microsoft.com";
Also I used the Microsoft.Graph nuget in the API project. And I am trying to create the GraphServiceClient using the code below
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var words = accessToken.Split(' ');
var token = words[1];
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}
And finally I am trying to access the user information using the code below,
public async Task<IEnumerable<Group>> GetGroupAsync(string accessToken)
{
var graphClient = GetClient(accessToken);
try
{
User me = await graphClient.Me.Request().GetAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var user= await graphClient.Users["***"].Request().Expand("MemberOf").GetAsync();
var userEmail = "testemail#test.com";
var usergroup = await graphClient.Users[userEmail].GetMemberGroups(false).Request().PostAsync();
var groupList = new List<Group>();
foreach (var g in usergroup.CurrentPage)
{
var groupObject = await graphClient.Groups[g].Request().GetAsync();
groupList.Add(groupObject);
}
return groupList;
}
But when I try the code I am getting the error "Microsoft.Graph.ServiceException: Code: InvalidAuthenticationToken
Message: Access token validation failure.Inner error at Microsoft.Graph.HttpProvider."
Can somebody help me please?
Thanks in advance
The access token passed to GetGroupAsync is not correct , and i am confused why you need to split the token :
var words = accessToken.Split(' ');
var token = words[1];
But never mind , since you have modified options.Resource = "https://graph.microsoft.com"; ADAL will help you get access token for Microsoft Graph API in OnAuthorizationCodeReceived function , and save the tokens to cache .
To get the access token , you could use ADAL to get the token from cache :
AuthenticationResult result = null;
// Because we signed-in already in the WebApp, the userObjectId is know
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
// Using ADAL.Net, get a bearer token to access the TodoListService
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
result = await authContext.AcquireTokenSilentAsync("https://graph.microsoft.com", credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Then you could pass that token to your function:
await GetGroupAsync(result.AccessToken);
Modify your GetClient function to delete the split part:
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}

Cookie-based Forms Authentication Across MVC 5 and ASP.NET Core applications

I have used forms authentication across different sites already, even between different versions of .NET, but now we're looking into starting a new project in ASP.NET 5 (MVC 6) ASP.NET Core and would like to use cookie-based forms authentication across both. The login is done in "old" MVC 5 application.
Is some cross-applications configuration for cookie-based forms authentication even possible or supported with current version of ASP.NET 5?
Could this be implemented on MVC6 ASP.NET Core side using FormsAuthenticationModule or can it play along somehow with the new authentication middleware? Any other suggestions?
I have been beating my head over this same problem for the last few days... but I have solved it... (it seems to be holding up)
This is for converting windows and later forms Authentication to forms Authentication for MVC5 and MVC6 so hopefully you can change enough code to make it work for you... I plan to change some parts when I re-write the login scripts. (and this is alpha so will be making some changes!)
I put the following code in our MVC5 Intranet Site to grab the Roles for windows Authentication
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
// Get current windows Identity to get the roles out of it
WindowsIdentity ident = WindowsIdentity.GetCurrent();
string[] roles = new string[ident.Groups.Count];
int i = 0;
// get the groups from the current Identity
foreach (var g in ident.Groups)
{
roles[i] = g.Translate(typeof(System.Security.Principal.NTAccount)).Value.ToString();
i++;
}
// join into a single string the roles that the user is a member of
string roleData = String.Join(";", roles) ;
// create the forms ticket that all MVC5 sites with the same machine key will pick up.
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, ident.Name, DateTime.Now, DateTime.Now.AddMinutes(30), false, roleData, "/");
string encTicket = FormsAuthentication.Encrypt(ticket);
// add the user name first from the Principle and add Windows as this will come from Windows Auth
roleData = ident.Name + ";" + "Windows;" + roleData;
//use machine key to encrypt the data
var encTicket2 = MachineKey.Protect(System.Text.Encoding.UTF8.GetBytes(roleData),
"Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware",
"ApplicationCookie", "v1");
//create a new cookie with a base64string of the encrypted bytes
HttpCookie hc2 = new HttpCookie("cookie1", Convert.ToBase64String(encTicket2));
hc2.Domain = ".domain.com";
hc2.Expires = DateTime.Now.AddHours(8);
Response.Cookies.Add(hc2);
// NOTE: The name of the HttpCookie must match what the FormsAuth site expects.
HttpCookie hc = new HttpCookie("cookie2", encTicket);
hc.Domain = ".domain.com";
hc.Expires = DateTime.Now.AddHours(8);
Response.Cookies.Add(hc);
// Ticket and cookie issued, now go to the FormsAuth site and all should be well.
Response.Redirect("http://www.yoursite.com");
}
this will create to a windows Authentication Ticket in both the forms and MVC6 method.
The string for MVC6 will look something like "John.Doe;Windows;Admin"
Then in the MVC6 startup file I have put the following code into the configure section...
app.Use(async (context, next) =>
{
Logger _logger = new Logger("C:\\\\Logs\\Log.txt");
try
{
var request = context.Request;
var cookie = request.Cookies.Get("cookie1");
var ticket = cookie.ToString();
ticket = ticket.Replace(" ", "+");
var padding = 3 - ((ticket.Length + 3)%4);
if (padding != 0)
ticket = ticket + new string('=', padding);
var bytes = Convert.FromBase64String(ticket);
bytes = System.Web.Security.MachineKey.Unprotect(bytes,
"Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware",
"ApplicationCookie", "v1");
string ticketstring = System.Text.Encoding.UTF8.GetString(bytes);
var ticketSplit = ticketstring.Split(';');
var claims = new Claim[ticketSplit.Length];
var OriginalIssuer = "";
for (int index = 0; index != ticketSplit.Length; ++index)
{
if (index == 0)
{
claims[index] = new Claim(ClaimTypes.Name, ticketSplit[index], "Windows");
}
else if (index == 1)
{
OriginalIssuer = ticketSplit[1];
}
else
{
claims[index] = new Claim(ClaimTypes.Role,ticketSplit[0], OriginalIssuer);
}
}
var identity = new ClaimsIdentity(claims, OriginalIssuer, ClaimTypes.Name,ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
_logger.Write(principal.Identity.Name);
context.User = principal;
_logger.Write("Cookie End");
await next();
} catch (Exception ex)
{
_logger.Write(ex.Message);
_logger.Write(ex.StackTrace);
}
});
This then takes the cookie and creates a new claims identity from it. I've only just finished the logic to get it working so I'm sure it can be tidied up... Just thought I would get it to you so you can see if you can get some ideas about it.
WebForms is not a part of ASP.NET 5. This is change #2 according to this blog post
Updated
The new lifecycle of ASP.NET MVC 6 uses a middleware to compose services. You can use Security package to authenticate, but the old "Forms" authentication is not supported anymore.
This is my simple code in Asp.net core mvc,hope will help:
In Startup.cs
In Function ConfigureServices add services.AddAuthorization(); after service.AddMvc()
In Function Configure add code like this
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "UserLoginCookie",
LoginPath = new PathString("/Account/Login"),
AccessDeniedPath = new PathString("/Account/Forbidden"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
before app.UseMvc....
In login Method:
core code like this:
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,userName here),
new Claim("UserCodeInMyWebApp",Anything you want),
new Claim(ClaimTypes.Role,"Admin")
};
var userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "UserLoginClaimsIdentity"));
//signin
await HttpContext.Authentication.SignInAsync("UserLoginCookie", userPrincipal, new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
IsPersistent = false,
AllowRefresh = false
});
return RedirectToAction("AuthPage", "Home");
then you can access claim value by keyvalue or check if authenticated:
bool flag = User.Identity.IsAuthenticated
ClaimsIdentity user = User.Identity as ClaimsIdentity
user.Name or user.FindFirst(the key value string you created).Value
and check like this:
[HttpGet]
[AllowAnonymous]
public IActionResult Index()
{
return View();
}
[Authorize(Roles = "Admin")]
[HttpGet]
public IActionResult AuthPage()
{
return View();
}
public IActionResult About()
{
return View();
}

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 to create Refresh Token with External Login Provider?

I have searched over the web and could not find a solution to my problem. I am implementing OAuth in my app. I am using ASP .NET Web API 2, and Owin. The scenario is this, once a user request to the Token end point, he or she will receive an access token along with a refresh token to generate a new access token. I have a class the helps me to generate a refresh token. Here is it :
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string> ("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15)
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
// this method will be used to generate Access Token using the Refresh Token
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
string hashedTokenId = Helper.GetHash(context.Token);
using (AuthRepository _repo = new AuthRepository())
{
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null )
{
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
// one refresh token per user and client
var result = await _repo.RemoveRefreshToken(hashedTokenId);
}
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
now i am allowing my users to register through facebook. Once a user register with facebook, I generate an access token and give it to him. Should I generate a refresh token as well ? Onething comes to my mind, is to generate a long access token like one day, then this user has to login with facebook again. But if i do not want to do that, I can give the client, a refresh token, and he can use it to refresh the generated access token and get a new. How do I create the refresh token and attach it to the response when someone register or login with facebook or externally ?
Here is my external registration API
public class AccountController : ApiController
{
[AllowAnonymous]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);
return Ok(accessTokenResponse);
}
}
// Private method to generate access token
private JObject GenerateLocalAccessTokenResponse(string userName)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
{
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
// Here is what I need
new JProperty("resfresh_token", GetRefreshToken()),
new JProperty("token_type", "bearer"),
new JProperty("refresh_token",refreshToken),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
}
I spent a lot of time to find the answer to this question. So, i'm happy to help you.
1) Change your ExternalLogin method.
It usually looks like:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Now, actually, it is necessary to add refresh_token.
Method will look like this:
if (hasRegistered)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
// ADD THIS PART
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
}
Now the refrehs token will be generated.
2) There is a problem to use basic context.SerializeTicket in SimpleRefreshTokenProvider CreateAsync method.
Message from Bit Of Technology
Seems in the ReceiveAsync method, the context.DeserializeTicket is not
returning an Authentication Ticket at all in the external login case.
When I look at the context.Ticket property after that call it’s null.
Comparing that to the local login flow, the DeserializeTicket method
sets the context.Ticket property to an AuthenticationTicket. So the
mystery now is how come the DeserializeTicket behaves differently in
the two flows. The protected ticket string in the database is created
in the same CreateAsync method, differing only in that I call that
method manually in the GenerateLocalAccessTokenResponse, vs. the Owin
middlware calling it normally… And neither SerializeTicket or
DeserializeTicket throw an error…
So, you need to use Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer to searizize and deserialize ticket.
It will be look like this:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
= new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
instead of:
token.ProtectedTicket = context.SerializeTicket();
And for ReceiveAsync method:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
instead of:
context.DeserializeTicket(refreshToken.ProtectedTicket);
3) Now you need to add refresh_token to ExternalLogin method response.
Override AuthorizationEndpointResponse in your OAuthAuthorizationServerProvider. Something like this:
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
{
context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
}
return base.AuthorizationEndpointResponse(context);
}
So.. thats all! Now, after calling ExternalLogin method, you get url:
https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL
I hope this helps)
#giraffe and others offcourse
A few remarks. There's no need to use the custom tickerserializer.
The following line:
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
As tokenformat: Startup.OAuthOptions.AccessTokenFormat is used. Since we want to provide a refeshtoken this needs te be changed to: Startup.OAuthOptions.RefreshTokenFormat
Otherwise if you want to get a new accesstoken and refresh the refreshtoken ( grant_type=refresh_token&refresh_token=...... ) the deserializer/unprotector will fail. Since it uses the wrong purposes keywords at the decrypt stage.
Finally found the solution for my problem.
First of all, if you EVER encounter any problems with OWIN and you cannot figure out what is going wrong, I advise you to simply enable symbol-debugging and debug it. A great explanation can be found here:
http://www.symbolsource.org/Public/Home/VisualStudio
My mistake simply was, that I was calculating a wrong ExiresUtc when using external login providers. So my refreshtoken basically was always expired right away....
If you are implementing refresh tokens, then look at this gread blog article:
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
And to make it work with refresh tokens for external providers, you have to set the two requried parameters ("as:clientAllowedOrigin" and "as:clientRefreshTokenLifeTime") on the context
so instead of
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
you need to get the client first and set the context parameters
// retrieve client from database
var client = authRepository.FindClient(client_id);
// only generate refresh token if client is registered
if (client != null)
{
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket);
// Set this two context parameters or it won't work!!
context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
}

Resources