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
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'm starting to use PayPal for payments with .net Core. I created a sandbox app and checked client id and secret. I get an error at content
"{"error":"invalid_client","error_description":"Client Authentication failed"}"
Code:
private async Task<PayPalAccessToken> GetPayPalAccessTokenAsync(HttpClient httpClient)
{
byte[] bytes = Encoding.GetEncoding("iso-8859-1")
.GetBytes($"{_configuration["PayPal:clientId"]} : {_configuration["PayPal:secret"]}");
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, "/v1/oauth2/token");
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(bytes));
var form = new Dictionary<string, string>
{
["grant_type"] = "client_credentials"
};
requestMessage.Content = new FormUrlEncodedContent(form);
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
string content = await responseMessage.Content.ReadAsStringAsync();
PayPalAccessToken accessToken = JsonConvert.DeserializeObject<PayPalAccessToken>(content);
return accessToken;
}
Setting:
"PayPal": {
"clientId": "xxx",
"secret": "xxx",
"urlAPI": "https://api-m.sandbox.paypal.com",
"returnUrl": "https://localhost:44370/cart/success",
"cancelUrl": "https://localhost:44370/cart/cancel"
}
And here is Code I followed https://gist.github.com/jakejscott/1b829ca1c9449e4788710867f346e90f
Full my code https://paste.mod.gg/ayoqinotis.csharp
What is the problem that I am facing?
I'm trying to convert my token string to jwt token using JwtSecurityTokenHandler. But it's getting error that saying
IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII is hidden]'.\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
How can I solve this issue?
Here is my token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwibmJmIjoxNTUwNjM3NzcxLCJleHAiOjE1NTA2Mzg5NzEsImlhdCI6MTU1MDYzNzc3MX0.tUcoyoHgkrX3rDKl0cRLd9FwLtRprQpgYepMoiekixY
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
Calling web api
using (HttpClient client = new HttpClient())
{
string path = "UserMaintenance/ValidateUserId?userid=" + txtUsername.Text.Trim().ToString();
client.BaseAddress = new Uri(GlobalData.BaseUri);
client.DefaultRequestHeaders.Add("Authorization", "Bearer" + GlobalData.Token);
HttpResponseMessage response = client.GetAsync(path).Result;
if (response.IsSuccessStatusCode)
{
var value = response.Content.ReadAsStringAsync().Result;
isValid = JsonConvert.DeserializeObject<bool>(value);
}
}
Here is my GetPrincipal method
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken securityToken;
var principal = handler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception ex)
{
return null;
}
}
This is how I do it and it works for me:
var token = new System.IdentityModel.Tokens.JwtSecurityToken(jwt);
The above line works for System.IdentityModel.Tokens.Jwt package version 4.0.0.
As #Nick commented, in the latest versions of the package, the JwtSecurityToken does not exist in the previous namespace anymore, instead it exists in System.IdentityModel.Tokens.Jwt so you need to write:
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(jwt);
Unless your token is not well-formed. It would be better if you share the token too.
Update:
You also need to remove the word "Bearer " from the beginning of the token (If you haven't):
var jwt = context.Request.Headers["Authorization"].Replace("Bearer ", string.Empty);
at version 5.6.0.0 - currently is the latest version
can use similar code as in #thilim9's question.
var tokenId = identity.Claims.SingleOrDefault(c => c.Type == "id_token")?.Value;
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.ReadJwtToken(tokenId);
For .net framework 4.5.1 I remove my custom key while generating token and use default values in claims of JwtRegisteredClaimNames.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.GivenName, Data.UserName),
new Claim(JwtRegisteredClaimNames.Prn,Data.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
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'm using asp.net boilerplate for my website. There I have standard authentication from aspnetboilerplate/module-zero(OWIN).
But now I need athentication for my windows phone app(wp8.1)
I was trying configure my application for authorization with bearer but I failed..
How configurate asp.net boilerplate application for my windows phone app auth?
In windows phone app I send post to my web api like this:
public static async Task<TokenResponseModel> GetBearerToken(string siteUrl, string Username, string Password)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(siteUrl);
client.DefaultRequestHeaders.Accept.Clear();
HttpContent requestContent = new StringContent("grant_type=password&username=" + Username + "&password=" + Password, Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage responseMessage = await client.PostAsync("Token", requestContent);
if (responseMessage.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
TokenResponseModel tokenResponse = (TokenResponseModel)JsonConvert.DeserializeObject(jsonMessage, typeof(TokenResponseModel));
return tokenResponse;
}
else
{
return null;
}
}
But what should I do in WebApi? How auth and next response bearer and how auth in next step using bearer when on class i have [AbpAuthorize]?
This now documented and implemented in module zero template
code:
In module WebApi:
Configuration.Modules.AbpWebApi().HttpConfiguration.Filters.Add(new HostAuthenticationFilter("Bearer"));
In controller WebApi:
[HttpPost]
public async Task<AjaxResponse> Authenticate(LoginModel loginModel)
{
CheckModelState();
var loginResult = await GetLoginResultAsync(
loginModel.UsernameOrEmailAddress,
loginModel.Password,
loginModel.TenancyName
);
var ticket = new AuthenticationTicket(loginResult.Identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
return new AjaxResponse(OAuthBearerOptions.AccessTokenFormat.Protect(ticket));
}
documentation: http://aspnetboilerplate.com/Pages/Documents/Zero/Startup-Template#token-based-authentication