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?
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'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;
}
I am developing a ASP.NET Web API using ASP.NET Identity (Individual Accounts) for authentication/authorization. I am able to successfully login by making a call to /token URI.
All I want is to automatically sign in my user when he register himself to my application. I am able to do the half of the task by signing in the user in Register method using following code:
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);
Is there a way I can return the same OAuth like response which I get when I make successful call to /token. Some thing like following:
{"access_token":"access-token","token_type":"bearer","expires_in":1209599,"userName":"username",".issued":"Sat, 22 Mar 2014 08:12:14 GMT",".expires":"Sat, 05 Apr 2014 08:12:14 GMT"}
Actually I'm facing same issue moments ago, I handled it in such an ugly way-- Inside Register method, I made another web request to access "/token", just after created the new user, and pass the latest username and password.
private string GetTokenForNewUser(string username, string password)
{
using (var client = new HttpClient())
{
var host = Request.RequestUri.Scheme + "://" + Request.RequestUri.Host + ":" + Request.RequestUri.Port;
client.BaseAddress = new Uri(host);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Dictionary<string, string> credential = new Dictionary<string, string>();
credential.Add("grant_type", "password");
credential.Add("username", username);
credential.Add("password", password);
HttpResponseMessage response = client.PostAsync(host + "token", new FormUrlEncodedContent(credential)).Result;
if (response.IsSuccessStatusCode)
{
return response.Content.ReadAsStringAsync().Result;
}
}
//if we go this far, something error happens
return string.Empty;
}
I don't think this is a good way to do so, but it just works.
You can add payload for the token (it also depends on the token you are using). Then, you can also retrieve the payload value from the token during request.
var token = new JwtSecurityToken(_config["Jwt:Issuer"], _config["Jwt:Issuer"], signingCredentials: credentials, expires: DateTime.Now.AddDays(2));
token.Payload["UserId"] = user.Id;
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
I am trying to authenticate inside integration test by calling FormsAuthentication.SetAuthCookie("someUser", false);
After that I do need to call WebAPI and not receive unauthorized exception because I have authorized attribute applied.
I am using this code to create auth cookie :
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
ticket.IsPersistent, userData.ToJson(), ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
/// Use existing cookie. Could create new one but would have to copy settings over...
cookie.Value = encTicket;
Now I want to add this cookie to HttpRequestMessage inside new HttpClient and send this with my regular request in integration test.
I don't know how to add that auth cookie to HttpRequestMessage ?
For manipulating cookies, you need to use WebRequestHandler along with HttpClient. For example,
var handler = new WebRequestHandler();
var client = new HttpClient(handler);
// use handler to configure request such as add cookies to send to server
CookieContainer property will allow to access cookies collection.
On different note, I doubt if creating FormsAuthentication cookie on client will work. A same encryption key would be needed on both client/server. The best approach would be to replay the login request for actual web API - most probably, it would be a POST to login page with user credentials. Observe the same over browser using tool such as Fiddler and construct the same request within your http client.
Almost 6 years late, but still may be helpful. The solution based on this one:
https://blogs.taiga.nl/martijn/2016/03/10/asp-net-web-api-owin-authenticated-integration-tests-without-authorization-server/
First, while creating Owin TestServer you have to create DataProtector:
private readonly TestServer _testServer;
public IDataProtector DataProtector { get; private set; }
public Server(OwinStartup startupConfig)
{
_testServer = TestServer.Create(builder =>
{
DataProtector = builder.CreateDataProtector(
typeof(CookieAuthenticationMiddleware).FullName, DefaultAuthenticationTypes.ApplicationCookie, "v1");
startupConfig.Configuration(builder);
});
}
Then generate cookie like this (use DataProtector created in previous step):
public string GeterateCookie()
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "your-role"),
new Claim(ClaimTypes.UserData, "user-data"),
new Claim(ClaimTypes.Name, "your-name")
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
var tdf = new TicketDataFormat(DataProtector);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties {ExpiresUtc = DateTime.UtcNow.AddHours(1)});
var protectedCookieValue = tdf.Protect(ticket);
var cookie = new CookieHeaderValue("yourCookie", protectedCookieValue)
{
Path = "/",
HttpOnly = true
};
return cookie.ToString();
}
Make sure to set required claims, initialize ClaimsIdentity according to settings provided to UseCookieAuthentication method, and setting correct CookieName.
The last step is to add CookieHeader to your request:
public Task<HttpResponseMessage> RequestAsync(HttpRequestMessage request)
{
request.Headers.Add("cookie", GenerateCookie());
return _client.SendAsync(request);
}