I've implemented external login providers in webapi by following this tutorial:
http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-social-logins-facebook-google-angularjs-app/
I wanted to also include refresh tokens to this solution. I managed to follow this article to do that in regular login scenario - http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
Unfortunately I have no idea how to do that for external logins. I've looked into implementation of OWIN and tried doing something like the code below. It generates tokens but I have trouble serializing ticket (ticket can't be unprotected when I try to make a request to generate new access token based on the refresh token generated this way).
private async Task<JObject> GenerateLocalAccessTokenResponse(string userId)
{
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
var user = await GetUser(userId);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("role", "user"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
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);
// This is what I added
var context = Request.GetOwinContext();
// Here I use a DataProtectionProvider which I generated on startup with app.GetDataProtectionProvider() and I keep it as a static object
var secureDataFormat = new TicketDataFormat(Helpers.Providers.DataProtectionProvider.Create(
typeof(OAuthAuthorizationServerMiddleware).Namespace, "Refresh_Token", "v1", "ASP.NET Identity"));
var createContext = new AuthenticationTokenCreateContext(context, secureDataFormat, ticket);
await Startup.OAuthServerOptions.RefreshTokenProvider.CreateAsync(createContext);
JObject tokenResponse = new JObject(
new JProperty("userName", user.UserName),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString()),
new JProperty("refresh_token", createContext.Token)
);
return tokenResponse;
}
Related
I have created a .net core console application to access the graph api. I created a authentication by using clientId and clientSecret of the Azure AD application
string tenantName = "MY.TENANT";
string authUrl = "https://login.microsoftonline.com/" + tenantName;
var clientId = "MYID";
var clientSecret = "MYSECRET";
AuthenticationContext authenticationContext = new AuthenticationContext(authUrl, false);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult;
authenticationResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com/", clientCred);
return authenticationResult.AccessToken;
After I get a valid token the call do a sharepoint list works fine and I get some data
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"{graphUrl}/sites/{siteId}/lists/MYLISTGUID/items?expand=fields");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
return responseString;
}
But if I call the Search API I get the following error: SearchRequest Invalid (Region is required when request with application permission.)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"{graphUrl}/search/query/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
}
}
}
};
request.Content = new StringContent(JsonConvert.SerializeObject(filter), Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
}
The same query by using the Graph Explorer works fine.
I found some posts around that tells something, that you can not call the search API by using the application credential but only by using delegation. In my case the api call is made by a service user and not by the user directly. I have to migrate a Sharepoint on Premise solution which access the search in that way.
Thanks for any input
You can get the region value by calling the following URL
https://yourtenant.sharepoint.com/_api/GeoTenantInstanceInformationCollection
Note: your tenant admin needs to call (copy&paste in the browser) this URL otherwise you will receive UnauthorizedAccessException with the message Current user is not a tenant administrator.
Then add region property with the value from the request above to your filter:
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
},
Region = "guid"
}
}
};
Resources:
Search content with application permissions
I am trying to store an email address while a page is running and refer to it in my controller. How can i do this? Currently i am doing this:
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, viewModel.EmailEntry),
new Claim(ClaimTypes.Role, "PasswordReset")
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
But i don't really want to use a cookie just to store the email address throughout the pages execution.
I have Asp.net web Application project with WebApi and individuals user Account.
I implemented Registration and login, and I used angularjs in front end.
Now, I need to store cookies in browser.
I'm confuse if authentication based on cookies store cookies in browser?
In my project I authenticate users based on token.
I do research but it confuse me and I did't find a clear guide to store cookies using Asp.Net Identity webApi.
Here is where is user authenticate:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
/*
I tried the following but I get an error that Request doesn't exist
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, context.UserName));
var id = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
authenticationManager.SignIn(id);
*/
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
I'm sorry, I'm new in Asp.net Identity.
If there is a clear guide to do this in Asp.Net Identity in WebApi (Not MVC)?
Note: I don't have LoginPath.
So you have a webApi that you use to authenticate your users and when your user is authenticated you get a jwt token, Right ? and you need to save that token in a cookie so that your front-end would use to make requests.
so here is a sample code i wrote to do just that is asp core api
in my aspcore API AccountsController => Login Action:
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginViewModel vm)
{
if (ModelState.IsValid)
{
var token = await _authenticationService.GenerateToken(vm);
if (token != null)
{
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
return StatusCode(StatusCodes.Status403Forbidden, new { ErrorMessage = "wrong Email or password" });
}
the _authenticationService is a class that i wrote to generatae jwt token :
public async Task<JwtSecurityToken> GenerateToken(LoginViewModel vm)
{
if (!string.IsNullOrEmpty(vm.Email) && !string.IsNullOrEmpty(vm.Password))
{
var user = await _userManager.FindByEmailAsync(vm.Email);
var userRoles = await _userManager.GetRolesAsync(user);
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Secrets:SigningKey").Value));
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, vm.Email)
};
foreach (var roleName in userRoles)
{
claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, roleName));
};
if (user != null && await _userManager.CheckPasswordAsync(user, vm.Password))
{
var token = new JwtSecurityToken(
issuer: _configuration.GetSection("Secrets:issuer").Value,
audience: _configuration.GetSection("Secrets:audience").Value,
expires: DateTime.UtcNow.AddDays(20),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
claims: claims);
return token;
}
return null;
}
return null;
}
so in the code above you if you send a good credentials to your api you will get a jwt token back.
now you need to save that token in a cookie, to do that you need to create the cookie in your front-end not your web api.
so you might see this link in angular-university
I'm currently working with ASP.net Web API with [Authorize] Attributes. Now instead of using the conventional way, I'm calling the following method to provide me an access token using which I can access the Web API methods.
[System.Web.Http.HttpPost]
public object GetAccessToken(string Id, string UserName, string Email)
{
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, UserName));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, Id));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Email, Email));
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
DateTime currentUtc = DateTime.UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
//ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(1));
ticket.Properties.ExpiresUtc = currentUtc.AddSeconds(20);
string accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
JObject token = new JObject(
new JProperty("userName", UserName),
new JProperty("userId", Id),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", currentUtc.AddSeconds(20).ToString()),
new JProperty("issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty("expires", currentUtc.AddSeconds(20).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
}
NOTE: This works fine.
The problem is that anyone can access this method just by simple HTTPost Request and have an access token as it is not secured.
Coming straight to the question: How can this method be made secure?
Im trying to secure my ASP.NET web api using OWIN and ASP.NET identity, I managed to get it done. But I am saving the access token in the client's local storage (Mobile) which defeats the purpose of the access token. So I have to add refresh token. I managed to generate the refresh token using the same ticket of the access token. But now I don't know how to use the refresh token in the client.
Startup.cs
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(tokenExpiry),
AllowInsecureHttp = true,
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
};
private static void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private static void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
AccountController.cs
private JObject GenerateApiToken(IdentityUser user, TimeSpan tokenExpirationTimeSpan, string provider)
{
var identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, provider));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY"));
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
var accesstoken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
var refreshtoken = Startup.OAuthOptions.RefreshTokenFormat.Protect(ticket);
Authentication.SignIn(identity);
// Create the response
JObject blob = new JObject(
new JProperty("userName", user.UserName),
new JProperty("access_token", accesstoken),
new JProperty("refresh_token", refreshtoken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
var json = Newtonsoft.Json.JsonConvert.SerializeObject(blob);
return blob;
}
Client request for bearer token
$.ajax({type: 'POST',
url: tokenUrl + "Token",
data: "grant_type=password&username=" + identity.userName + "&password=" + identity.password,
contentType: 'application/x-www-form-urlencoded',
}).
done(function(response) {
app.tokenManager.saveToken(response.access_token, response.refresh_token, response.expires_in, apiTokenType.BASIC);
deferred.resolve({
token: response.access_token
});
})
.fail(function(result, status) {
deferred.reject(result);
});
Now, how can I use the Refresh token?
according to aouth2 spec
https://www.rfc-editor.org/rfc/rfc6749#section-6
try
POST /token HTTP/1.1
Host: server.example.com
Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA