I have the following code that creates a JWT token in a webapi core application.
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username!=null?userInfo.Username:string.Empty),
new Claim(JwtRegisteredClaimNames.Email, userInfo.Email!=null?userInfo.Email:string.Empty),
new Claim(JwtRegisteredClaimNames.Jti, userInfo.AccountId!=null?userInfo.AccountId.ToString():Guid.Empty.ToString()),
new Claim(JwtRegisteredClaimNames.NameId, userInfo.UserId!=null?userInfo.UserId.ToString():Guid.Empty.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, userInfo.ClientId!=null?userInfo.ClientId.ToString():Guid.Empty.ToString())
};
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
null,
expires: DateTime.Now.AddMinutes(20),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
However when I try to get the claims back after the client has called a webapi end point, I only have the following claims. As you can see the "JTI" claim is fine, but the "UniqueName" claim is missing.
[0]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: }
[1]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: }
[2]: {jti: f7c5af77-d0c4-4026-9b33-2fe9fbf5ee28}
[3]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: 00000000-0000-0000-0000-000000000000}
[4]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: b34a1b42-df24-42a3-93d7-68e88523602f}
[5]: {exp: 1581973428}
[6]: {iss: Test.com}
[7]: {aud: Test.com}
What do I need to do to get the claim to appear?
Just to note that the claim that I set as JwtRegisteredClaimNames.UniqueName does appear in the claims but not under that name. Its value is set under the item
[4]: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name: b34a1b42-df24-42a3-93d7-68e88523602f}
However this is not brought back when use the following as it is null
identity.FindFirst(JwtRegisteredClaimNames.UniqueName).Value;
However this is not brought back when use the following as it is null
identity.FindFirst(JwtRegisteredClaimNames.UniqueName).Value;
To fix the above issue, you can try to clear the inbound claim type map using following code snippet in Startup.cs
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
Test Result
Set testing value for JwtRegisteredClaimNames.UniqueName
//just for testing purpose
new Claim(JwtRegisteredClaimNames.UniqueName, "b34a1b42-df24-42a3-93d7-68e88523602f")
Can get expected value using identity.FindFirst(JwtRegisteredClaimNames.UniqueName).Value
Related
Trying to stream a video from Azure Media Service into a Xamarin.Form application.
The asset is protected with a JWT token.
I use the following code to generate the token:
private string GetJWT(string PrimaryKey) {
var tokenSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(PrimaryKey));
SigningCredentials cred = new SigningCredentials(tokenSigningKey, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new JwtSecurityToken(
issuer: "xxx",
audience: "yyy",
claims: null,
notBefore: DateTime.Now.AddMinutes(-5),
expires: DateTime.Now.AddMinutes(60),
signingCredentials: cred);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
return handler.WriteToken(token);
}
The URI for the asset is like : https://xxx.streaming.media.azure.net/12222-1565-232323-a5b8-c10e148273ae/Test.ism/manifest(format=m3u8-cmaf,encryption=cbc)
If I use Azure Media Player (https://ampdemo.azureedge.net) to test the URI and the AES token, it works fine. So I guess there is no problem with the token itself...
The documentation (https://learn.microsoft.com/en-us/azure/media-services/latest/security-pass-authentication-tokens-how-to#pass-a-token-via-the-addition-of-a-url-query-parameter-with-tokentokenvalue) says that the following code should work to send the token directly with the url.
I need to do this as with Xamarin.Forms and MediaElement, I can't send the token in a header request. So I need the querystring option...
string armoredAuthToken = System.Web.HttpUtility.UrlEncode(authToken);
string uriWithTokenParameter = string.Format("{0}&token={1}", keyDeliveryServiceUri.AbsoluteUri, armoredAuthToken);
Uri keyDeliveryUrlWithTokenParameter = new Uri(uriWithTokenParameter);
player.Source = keyDeliveryUrlWithTokenParameter; (player is a MediaElement control)
But the video is never loaded.
In my opinion there is an error, it should be {0}?token={1} instead of {0}&token={1}.
But that doesn't work neither.
If I test with VLC the https://xxx.streaming.media.azure.net/12222-1565-232323-a5b8-c10e148273ae/Test.ism/manifest(format=m3u8-cmaf,encryption=cbc)?token=zzzzz, it doesn't work neither.
I presume there is a problem with token in the querystring, as if Azure can't read it.
I have implemented the custom security on my reporting services 2016 and it displays the login page once the URL for reporting services is typed on browser URL bar (either reports or reportserver)
I am using the following code to pass the Credentials
when i use the code WITHOUT my security extension it works and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"NTLM", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
when i use the code WITH the security extension it doesnt work and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"Basic", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
and i get an Exception saying "The response to this POST request did not contain a 'location' header. That is not supported by this client." when i actually use this credentials
Is "basic" the wrong option ?
Have anyone done this ?
Update 1
Well it turns out that my SSRS is expecting an Authorisation cookie
which i am unable to pass (according to fiddler, there is no cookie)
HttpWebRequest request;
request = (HttpWebRequest)HttpWebRequest.Create("http://mylocalcomputerwithRS/Reports_SQL2016/api/v1.0");
CookieContainer cookieJar = new CookieContainer();
request.CookieContainer = cookieJar;
Cookie authCookie = new Cookie("sqlAuthCookie", "username:password");
authCookie.Domain = ".mydomain.mylocalcomputerwithRS";
if (authCookie != null)
request.CookieContainer.Add(authCookie);
request.Timeout = -1;
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
That's how I got it (SSRS 2017; api v2.0). I took the value for the "body" from Fiddler:
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
Assert.AreEqual(0, handler.CookieContainer.Count);
// Create a login form
var body = new Dictionary<string, string>()
{
{"__VIEWSTATE", "9cZYKBmLKR3EbLhJvaf1JI7LZ4cc0244Hpcpzt/2MsDy+ccwNaw9hswvzwepb4InPxvrgR0FJ/TpZWbLZGNEIuD/dmmqy0qXNm5/6VMn9eV+SBbdAhSupsEhmbuTTrg7sjtRig==" },
{"__VIEWSTATEGENERATOR", "480DEEB3"},
{ "__EVENTVALIDATION", "IS0IRlkvSTMCa7SfuB/lrh9f5TpFSB2wpqBZGzpoT/aKGsI5zSjooNO9QvxIh+QIvcbPFDOqTD7R0VDOH8CWkX4T4Fs29e6IL92qPik3euu5QpidxJB14t/WSqBywIMEWXy6lfVTsTWAkkMJRX8DX7OwIhSWZAEbWZUyJRSpXZK5k74jl4x85OZJ19hyfE9qwatskQ=="},
{"txtUserName", "User"},
{"txtPassword", "1"},
{"btnLogin","Войти"}
};
var content = new FormUrlEncodedContent(body);
// POST to login form
var response = await httpClient.PostAsync("http://127.0.0.1:777/ReportServer/Logon.aspx", content);
// Check the cookies created by server
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var cookies = handler.CookieContainer.GetCookies(new Uri("http://127.0.0.1:777/ReportServer"));
Assert.AreEqual("sqlAuthCookie", cookies[0].Name);
// Make new request to secured resource
var myresponse = await httpClient.GetAsync("http://127.0.0.1:777/Reports/api/v2.0/Folders");
var stringContent = await myresponse.Content.ReadAsStringAsync();
Console.Write(stringContent);
As an alternative you can customize SSRS Custom Security Sample quite a bit.
I forked Microsoft's Custom Security Sample to do just what you are describing (needed the functionality at a client long ago and reimplemented as a shareable project on GitHub).
https://github.com/sonrai-LLC/ExtRSAuth
I created a YouTube walkthrough as well to show how one can extend and debug SSRS security with this ExtRSAuth SSRS security assembly https://www.youtube.com/watch?v=tnsWChwW7lA
TL; DR; just bypass the Microsoft example auth check in Login.aspx.cs and put your auth in Page_Load() or Page_Init() event of Login.aspx.cs- wherever you want to perform some custom logging check- and then immediately redirect auth'd user to their requested URI.
My company is looking into reporting on Azure. We only want our customers to give us read only credentials for us to use. I did some research and it looks like Azure Active Directory does just that. So I'm looking to authenticate using a read only Azure Directory Application.
To get me started I was following this blog on using the Management API via Azure Active Directory.
https://msdn.microsoft.com/en-us/library/azure/dn722415.aspx
Aside from the approach show being very unfriendly, it doesn't work =(
I get this error after logging in as a global administrator:
"AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'."
Did some research and found this style of authentication was for native app and NOT web apps (despite what the blog post saying other wise..). So I made a tweak. My GetAuthorizationHeader now looks like this:
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
I am able to get the Access Token (yay). But now when I try to use this with the Azure Management library client I get this error:
"ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription."
I double checked my permissions in my application. It looked good. I tried giving full access to everything to see if that would have made a difference.
I double checked my tenantId, clientId, and subscriptionId, all looked good.
I made sure the subscription I'm using is pointed to the AD my application is in.
I tried making a new secret key.
My guess is this is the issue:
However in this UI I am unable to select any values for that property. I'm unsure if this is the result of a bug or an unfinished feature.
Am I missing something here?
Thanks
Here's my full code for reference:
class Program
{
static void Main(string[] args)
{
var token = GetAuthorizationHeader();
var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);
using (var computeClient = new ComputeManagementClient(credential))
{
var images = computeClient.VirtualMachineOSImages.List();
}
}
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
}
EDIT:
Progress has been made. As I discussed with Gaurav, I needed to ditch the Azure Management Library because as of right now it does not seem to support Azure Resource Manager (ARM) API! So instead I did raw web requests. And it works as intended. If I remove role access off my AD Application I get access denied. When I have it I get back data.
One thing I'm not sure about is making it so my application is auto-adding to new resources.
Also, Is there a way to list Resource Groups that are accessible for my AD Application?
New code:
class Program
{
static void Main(string[] args)
{
var token = GetAuthorizationHeader();
string subscriptionId = ConfigurationManager.AppSettings["subscriptionId"];
string resourceGroupName = ConfigurationManager.AppSettings["resourceGroupName"];
var uriListMachines = string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/virtualmachines?api-version=2015-05-01-preview", subscriptionId, resourceGroupName);
var t = WebRequest.Create(uriListMachines);
t.ContentType = "application/json";
t.Headers.Add("Authorization", "Bearer " + token);
var response = (HttpWebResponse)t.GetResponse();
string result = "";
using (var reader = new StreamReader(response.GetResponseStream()))
{
result = reader.ReadToEnd();
}
//Original Attempt:
//var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);
//using (var client = CloudContext.Clients.CreateComputeManagementClient(credential))
//{
// var images = client.VirtualMachineVMImages.List();
//}
}
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
}
EDIT EDIT:
I figured out my hung up. Resources created in the OLD portal will get it's own distinct resource group.
From what I can tell you can not add a resource made in the old portal existing resource group (boooo). Resources created in the new portal will be able to assign the resource to an existing group (aka one that gives a role access to my AD Application).
This is such a mess! But at least I know what is going on now.
I believe you're on the right track as to why you're running into this problem.
Here's what's happening:
Essentially permission to execute Service Management API is a delegated permission and not an application permission. In other words, the API is executed in context of the user for which the token is acquired. Now you are getting this token for your application (specified by client id/secret). However your application doesn't have access to your Azure Subscription because the user record created for this application in your Azure AD is of type Service Principal. Since this Service Principal doesn't have access to your Azure Subscription, you're getting this Forbidden Error (I must say that the error is misleading because you're not using certificate at all).
There are a few things you could do:
Switch to Azure Resource Manager (ARM) API - ARM API is the next generation of Service Management API (SM API) and Azure is moving towards this direction only. It exclusively works off of Azure AD token. If possible, make use of that to manage your Azure resources (though you need to keep in mind that as of today not all Azure resources can be managed through ARM API). They way you do it is take your Service Principal and assign it to a particular role using new Azure Portal. Please see this link for more details on this: https://azure.microsoft.com/en-in/documentation/articles/resource-group-create-service-principal-portal/.
Use X509 Certificate - You can always use X509 Certificate based authorization to authorize your SM API requests. Please see this link for more details on that: https://msdn.microsoft.com/en-us/library/azure/ee460782.aspx#bk_cert. The downside of this approach is that the application (or whosoever has access to this certificate) will get full access to your Azure Subscription and can do everything there (including deleting resources).
Acquire token for a user instead of an application - This is another approach you can take. Essentially ask your users to login into Azure AD through your console application and acquire token for that user. Again, please keep in mind that this user must be a Co-Admin in your Azure Subscription and will have full access to your Azure Subscription as with SM API there's no concept of Role-based access control.
We can successfully acquire a token using the following code:
var certificate = Certificate.Load("Client.pfx", "notasecret");
var authenticationContext = new AuthenticationContext(authority);
var clientAssertionCertificate = new ClientAssertionCertificate(clientId, certificate);
return await authenticationContext.AcquireTokenAsync(resource, clientAssertionCertificate);
The token doesnt seem to contain any information that we can use to identity the client. In our use case we have lots of daemon service clients that communicate to a API. We need to have some unique identified available on the server.
I also tried creating our own JWT token and added some public claims, such as name. However after requesting client assertion type using the following code fragment
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "clientid", clientId },
{ "resource", resource },
{ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{ "grant_type", "client_credentials" },
{ "client_assertion", jwt }
});
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://login.windows.net/{guid}/")
};
var response = await httpClient.PostAsync("oauth2/token", content);
The return token had none of my custom information.
Question: Is there a way to pass custom claims using ClientAssertionCertificate flow? where the token returned has additional information.
There is currently no way of adding custom claims in tokens issued for applications.
The token you receive should contain the claims appid (which identifies the client_id of the application who requested the token) and tid (which indicates which azure AD tenant the app is operating on). Those two should be enough for you to identify the calling application. Now, if rather than the application you want to identify the process (as in, instance of application X running on server A and instance of application X running on server B) then I don't believe we have anything in Azure AD today that would help you to tell the two apart - for Azure AD if they have the same client_id and secret, they are the same application.
I'm looking for a way to exchange short-lived access tokens for long-lived access tokens in our backend as described by Facebook here. How to do this with facebook4j?
I have done something like this to exchange old token for new token:
private AccessToken refreshToken(Facebook facebook, AccessToken currentToken) throws Exception {
String clientId = configuration.getString(ConfigurationKeys.SOCIAL_FACEBOOK_CLIENTID);
String clientSecret = configuration.getString(ConfigurationKeys.SOCIAL_FACEBOOK_CLIENTSECRET);
Map<String, String> params = new HashMap<String, String>();
params.put("client_id", clientId);
params.put("client_secret", clientSecret);
params.put("grant_type", "fb_exchange_token");
params.put("fb_exchange_token", currentToken.getToken());
RawAPIResponse apiResponse = facebook.callGetAPI("/oauth/access_token", params);
String response = apiResponse.asString();
AccessToken newAccessToken = new AccessToken(response);
facebook.setOAuthAccessToken(newAccessToken);
return newAccessToken;
}
I think this can be done after each login so the access token is refreshed even if it still valid - you will just get newer token with 60 days of validity.
What do you think?
I am extending the Facebook class. The method they provided don't work. So I wrote another function which does gives a long lived token but it's somehow invalid ( I tried testing the new token with token_debug and tried to generate client_code with it)! I will update you if I get to work it out. If you can solve it, please update me.
Please remember I didn't clean up the code as I am still writing on it.
public function GetExtendedAccessToken()
{
//global $CONFIGURATIONS;
//$info=$this->api($path,'GET',$args);//doesn't work as api overrides method to post
$string=file_get_contents("https://graph.facebook.com/oauth/access_token?client_id=".$this->getAppId()
."&client_secret=".$this->getAppSecret()
."&fb_exchange_token=".$this->getAccessToken()
."&grant_type=fb_exchange_token"
."&redirect_uri=".$redirectUri);
var_dump($string);
$tokenInfo=explode('&',$string);
$exAccessToken=str_replace('access_token=', '', $tokenInfo[0]);
$expiresAt=str_replace('expires=', '', $tokenInfo[1]);
echo "expires in ". (time()-$expiresAt);
var_dump($exAccessToken);
return $exAccessToken;
}
It works now. Some times I get an error for not providing redirect_uri.