WIF SAML RequestSecurityToken STS Internal server error - security

I try to reach my STS to request a token. The code is based on a blog post by #leastprivilege : WCF and Identity in .NET 4.5: External Authentication with WS-Trust. I use the explicit approach (by code).
private static SecurityToken RequestSecurityToken()
{
// set up the ws-trust channel factory
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential),
"https://federation.mydomain/adfs/services/trust/mex") { TrustVersion = TrustVersion.WSTrust13 };
//factory.Credentials.SupportInteractive = false;
factory.Credentials.UserName.UserName = "user-pcote";
factory.Credentials.UserName.Password = "123456";
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference("https://myRP/")
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}
I can see the XML when copying the STS endpoint adress in my browser, therefore the federation server is reachable. But I always get an internal server error (500) as soon as I issue the token request. Does anybody have an idea what could be my problem here.

Finally managed to get it working by changing the KeyType to KeyTypes.Bearer (since there's no certificate applied to the RP in AD FS). I based myseflf on this website that gives a good explanations on how it all relates :
http://blog.skadefro.dk/2011/09/claimsbased-authentication-and-wcf.html
if we look in Microsoft.IdentityModel.SecurityTokenService.KeyTypes we
see we can use Asymmetric, Symmetric or Bearer. Tons of post out there
about this.
If you use Asymmetric you as requestor need to supply a key to encrypt
the claims with. ( set "UseKey” )
If you use Symmetric the identity provider have all ready been told
what certificate to use, to encrypt the claims with.
If you choose Bearer. The token get signed, but claims will not be
encrypted. If a token signing certificate have been assigned on the
Relying Party, claims will simply not be included at all.
When you request a token, the token gets signed (not encrypted) with a
certificate installed on the Identity Provider ( ADFS ). If you add a
certificate on a Relying Party Trust (RP) on the ADFS server, the
claims inside the token gets encrypted with with that certificate.
Only host/applications that have access to the private key of that
certificate can now decrypt the token and read the claims. You don’t
need to read the claims in order to authenticate your self. For
instance if you have a WCF Service you want to call from within an
application. You can from within that application still request a
token from the ADFS server and then access the WCF service with that
Token. As long as the WCF service have access to the private key and
can read the claims, your application don’t need it.
private static SecurityToken RequestSecurityToken()
{
var binding = new UserNameWSTrustBinding(
SecurityMode.TransportWithMessageCredential);
var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri("<your_adfs_uri>/adfs/services/trust/13/usernamemixed"), EndpointIdentity.CreateSpnIdentity("host/your.spn.com"))) { TrustVersion = TrustVersion.WSTrust13 };
factory.Credentials.UserName.UserName = "username";
factory.Credentials.UserName.Password = "password";
// create token request
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(<uri_of_your_relying_party>)
};
var channel = factory.CreateChannel();
try
{
var response = channel.Issue(rst);
return response ;
}
catch (Exception e)
{
var message = e.Message;
return null;
}
}

I managed to find the right endpoint (which was /adfs/services/trust/13/usernamemixed) but now I get the following error :
ID4007: The symmetric key inside the requested security token must be encrypted

Related

How to access two separate Web APIs protected using Azure AD B2C from a web app

We have two separeate dotnet core apis(API1 & API2) that are protected using azure ad b2c. Both these apis are registered on the b2c tenant and have their scopes exposed.
We have a client web applicaiton that is to access the above protected apis. This web app has been registered as a applicaiton in b2c tenant and has api permissions set for the above apis with proper scopes defined.
We use MSAL.net with a signinpolicy to sign the user in to the web app.
the authentication call requires scopes to mentioned. So we add API1's scope in the call.
(note : one scope of a single resource can be added in a auth call shown below)
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
The OnAuthorizationCodeRecieved method in Startup.Auth.cs recieved the code recieved as a result of above auth call and uses it to get a access token based on the scopes provided and stores it in the cache. shown below
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
}
catch (Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Unable to get authorization code {ex.Message}.".Replace("\n", "").Replace("\r", "")
});
}
}
This access token is then used in the TasksController to call AcquireTokenSilent which retrieves the access token from the cache, which is then used in the api call.
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Globals.ReadTasksScope };
IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Handle the response
switch (response.StatusCode)
{
case HttpStatusCode.OK:
String responseString = await response.Content.ReadAsStringAsync();
JArray tasks = JArray.Parse(responseString);
ViewBag.Tasks = tasks;
return View();
case HttpStatusCode.Unauthorized:
return ErrorAction("Please sign in again. " + response.ReasonPhrase);
default:
return ErrorAction("Error. Status code = " + response.StatusCode + ": " + response.ReasonPhrase);
}
}
catch (MsalUiRequiredException ex)
{
/*
If the tokens have expired or become invalid for any reason, ask the user to sign in again.
Another cause of this exception is when you restart the app using InMemory cache.
It will get wiped out while the user will be authenticated still because of their cookies, requiring the TokenCache to be initialized again
through the sign in flow.
*/
return new RedirectResult("/Account/SignUpSignIn?redirectUrl=/Tasks");
}
catch (Exception ex)
{
return ErrorAction("Error reading to do list: " + ex.Message);
}
}
The issue is the code recieved by the OnAuthorizationCodeRecieved method can only be used to get the access token for API1 since its scope was mentioned in auth call. When trying to get access token for API2 it returns null.
Question : How to configure the web app so that it is able to access multiple protected apis?
Please suggest.
The code can be found from the sample https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
A single access token can only contain scopes for a single audience.
You have 2 options:
Combine both services into a single app registration and expose different scopes.
Request multiple tokens - one per service. If your SSO policy is configured correctly in B2C, this should happen silently unbeknownst to the user.
I recommend using option 1 if you own both services (which it sounds like you do). A few tips related to this option.
When declaring the scopes in the combined app registration, use the dot-syntax {LogicalService}.{Operation}. If you do this, the scopes will be grouped by logical service within the Azure portal.
Make sure you are validating scopes in your service. Validating only the audience is not good enough and would allow an attacker to make lateral movements with a token bound for another service.

Does Keycloak allow obtaining id tokens via web interface

I am investigating how to possibly authenticate to a Kubernetes 1.13 cluster with OpenID Connect and Keycloak. I am new to this area.
This YouTube video ("Use Open ID Connect for Kubernetes API server") accomplishes part of what I want. An id token is initially obtained by making a HTTP request (with curl) to Keycloak citing grant type password. The resulting token is then subsequently used in further HTTP requests to the Kubernetes API. This works but has the disadvantage that clients directly handle users' permanent credentials.
Would it not be better if the token were issued by a secure web page that also required authentication via Keycloak (this time with grant type authorization code) and did nothing else but displaying a new token? Such tokens (transient credentials) could then e.g. be manually copied into kubeconfigs for further use?
Does Keycloak provide such interactive web pages (next to the REST endpoints for obtaining tokens programatically) or is this out of scope? If the second, are there other standard components for such tasks?
UPDATE This illustration from the Kubernetes documentation perhaps makes more clear what I am seeking. In step 1 a user should log into the Identity provider to obtain tokens which can then be configured into kubectl. Does Keycloak support this step, i.e. offer a web page where users could log in to obtain their tokens?
If I am able to understand your question ,so you want to get the accesstoken via Java code so here is code you can write and call
String obtainAccessToken = obtainAccessToken(username, password);
putRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
putRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
Here is the method you should call
public String obtainAccessToken(String UserName, String pwd)
{
AuthzClient authzClient = AuthzClient.create(configuration);
AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken(UserName, pwd);
String token = accessTokenResponse.getToken();
return token;
}
Here is the get realm method
public Response getAllRealms() {
ObjectMapper mapper = JacksonObjectMapperProvider.getObjectMapper();
CloseableHttpResponse response = null;
List<SureRealmRepresentation> realmList = new ArrayList<SureRealmRepresentation>();
int status;
try {
String urlGetAllRealms = URL + "/admin/realms";
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet getRequest = new HttpGet(urlGetAllRealms);
String obtainAccessToken = obtainAccessToken(username, password);
getRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
getRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
response = httpclient.execute(getRequest);
status = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (status == 200) {
RealmRepresentation[] realmArray = mapper.readValue(responseBody, RealmRepresentation[].class);
}
catch (Exception e) {
if (e instanceof Exception) {
throw (Exception) e;
} else {
throw ErrorHandler.wrap(new Exception("EroorType : "+ e.toString()));
}
}

Authenticate to multiple Azure services using Service Principle (.net Core)

I need to get access to Key Vault and Service Bus from code, using a Service Principle for authentication.
I can use the following code to access Service Bus, which works as expected - when I enable to Service Principle in the Access Policies I can pull the list of topics:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var serviceBusManager = ServiceBusManager.Authenticate(credentials, SUBSCRIPTIONID);
var serviceBusNamespace = serviceBusManager.Namespaces.List().SingleOrDefault(n => n.Name == "SERVICEBUSNAMESPACE");
var topics = serviceBusNamespace.Topics.ListAsync().GetAwaiter().GetResult();
However, I also need to get some information from Key Vault and I was trying to establish a common way to authenticate.
METHOD 1
Similar to the above, I tried this code to access KeyVault:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(APPID, APPSECRET, TENANTID, AzureEnvironment.AzureGlobalCloud);
var kvManager = new KeyVaultClient(credentials);
var secret = kvManager.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult().Value;
I get the the following error:
Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: 'Operation
returned an invalid status code 'Unauthorized''
METHOD 2
This code does work for Key Vault however (showing I have correct permissions):
string GetSecret()
{
var client = new KeyVaultClient(GetAccessToken);
var secret = client.GetSecretAsync("https://VAULTNAMESPACE.vault.azure.net", "SECRETNAME").GetAwaiter().GetResult();
return secret;
}
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
var credential = new ClientCredential(appId, appSecret);
var tokenResult = await context.AcquireTokenAsync("https://vault.azure.net", credential);
return tokenResult.AccessToken;
}
But, again, it's a very KeyVault specific way to Authenticate and I was hoping to establish a common mechanism using SdkContext.AzureCredentialsFactory. Any reason why I'd be getting an Unauthorized exception with the code above connecting to Key Vault? (all is set up correctly in Azure).
Thanks for any tips!
When you use SdkContext.AzureCredentialsFactory.FromServicePrincipal to authenticate, it will use https://management.azure.com/ as its Resource Uri.
While Azure Key Vault has its own authorization system and its Resource Uri is https://vault.azure.net, so you may get the Unauthorized error message.
So, you could use Method2 to get access to Azure Key Vault with right Resource Uri.
For more details, you could refer to this article.

IdentityServer4 invalid_token "The issuer is invalid" on Azure, working on localhost

Help please, I'm building a .NET Core API with ionic front end. I want to use ASPNET Core Identity so I was more or less following this example
https://identityserver4.readthedocs.io/en/release/quickstarts/6_aspnet_identity.html
here is what I have in Startup.cs
// Adds IdentityServer
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients(Configuration))
.AddAspNetIdentity<ApplicationUser>();
and
app.UseIdentity();
app.UseIdentityServer();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = API_address,
RequireHttpsMetadata = false,
ApiName = "myAPIs"
});
and in my Config.cs file for in memory configurations I have
public class Config
{
// scopes define the resources in your system
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(
"myAPIs", // Api resource name
"My API Set #1", // Display name
new[] { JwtClaimTypes.Name, JwtClaimTypes.Role }) // Claims to be included in access token
};
}
// client want to access resources (aka scopes)
public static IEnumerable<Client> GetClients(IConfigurationRoot configuration)
{
return new List<Client>
{
new Client
{
ClientId = "myClient",
ClientName = "My Custom Client",
AllowedCorsOrigins = new List<string>
{
"whateverINeedHere"
},
AccessTokenLifetime = 60 * 60 * 24,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequireClientSecret = false,
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes =
{
"myAPIs"
}
}
};
}
}
Now the problem is that when I test this locally, everything works just fine.
I hit the /connect/token endpoint, I get a token response, hit the controller that needs token authorization and my claims are there. But when I deploy it to Azure, when I want to use the token (issued from that environment) I get 401 Unauthorized with response header invalid_token "The issuer is invalid". I've Googled, but people get invalid tokens with signature problems, not issuer. I've never used identity server before and to me this looks like it's some configuration problem. I have compared tokens I get from identity server on jwt.io, they look exactly the same, only difference being the issuer localhost -> myAPIAddress.
Can someone point me to the right direction?
This smells like it could be the temporary signing credentials. I also ran into problems when deploying to Azure when my cert wasn't loading.
I suggest you create a self signed cert and add it to azure using the following instructions. (Note this can be done in the new portal).
https://azure.microsoft.com/en-us/blog/using-certificates-in-azure-websites-applications/
REMEMBER: Make sure you add the WEBSITE_LOAD_CERTIFICATES application setting!
Also for your benefit, here's the code I use to load the cert in my startup.cs. I keep a copy of the cert in the repository so I can load it from disk as a fallback (when I'm on my dev machine).
X509Certificate2 cert = null;
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
"A9781679661914B7539BE020EE9C4F6880579F42",
false);
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
cert = certCollection[0];
// Use certificate
Log.Logger.Information($"Successfully loaded cert from registry: {cert.FriendlyName}");
}
}
// Fallback to local file for development
if (cert == null)
{
cert = new X509Certificate2(Path.Combine(_env.ContentRootPath, "myauth.pfx"), "mypassword");
Log.Logger.Information($"Falling back to cert from file. Successfully loaded : {cert.FriendlyName}");
}
services.AddIdentityServer()
.AddSigningCredential(cert)
Could be you've got an SSL/TLS issue between client and IdentityServer, are you able to view logged exceptions from IdentityServer itself? You may see something like:
"... Could not establish trust relationship for the SSL/TLS..."
If you're running IdentityServer on HTTPS you need to make sure you've got its domain/sub-domain in your certificate.
Either way, IdentityServer logs lots of useful info so turn on logging and check out what it says, that should point you in the right direction.

context.Request.User is null in OWIN OAuthAuthorizationServerProvider

I'm trying to implement OAuth using OWIN for a Web API v2 endpoint on my local intranet. The API is hosted in IIS using built-in Windows Authentication. In short, this is what I want to happen.
When I ask for my Token at /token
Pull the WindowsPrincipal out of the OWIN context
Use the SID from the WindowsPrincipal to look up some roles for this
user in a SQL table.
Create a new ClaimsIdentity that stores the username and roles
Turn that into a Json Web Token (JWT) that I sent bak
When I request a resource from my API using my token
Convert the JWT Bearer token back to the ClaimsIdentity
Use that ClaimsIdentity for authorizing requests to the resource by
role
This way I don't have to do a database lookup for user roles on each
request. It's just baked into the JWT.
I think I'm setting everything up correctly. My Startup.Configuration method looks like this.
public void Configuration(IAppBuilder app)
{
// token generation
// This is what drives the action when a client connects to the /token route
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
// for demo purposes
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
AccessTokenFormat = GetMyJwtTokenFormat(),
Provider = new MyAuthorizationServerProvider()
});
//// token consumption
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions()
{
Realm = "http://www.ccl.org",
Provider = new OAuthBearerAuthenticationProvider(),
AccessTokenFormat = GetMyJwtTokenFormat()
}
);
app.UseWebApi(WebApiConfig.Register());
}
MyAuthorizationServerProvider looks like this...
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Since I'm hosting in IIS with Windows Auth enabled
// I'm expecting my WindowsPrincipal to be here, but it's null :(
var windowsPrincipal = context.OwinContext.Request.User.Identity;
// windowsPrincipal is null here. Why?
// Call SQL to get roles for this user
// create the identity with the roles
var id = new ClaimsIdentity(stuff, more stuff);
context.Validated(id);
}
}
My problem is that context.Request.User is null here. I can't get to my WindowsPrincipal. If I create some other dummy middleware, I can get to the WindowsPrincipal without issue. Why is it null in this context? Am I doing something wrong?
Swap the order of UseOAuthAuthorizationServer and UseOAuthBearerAuthentication. UseOAuthBearerAuthentication calls UseStageMarker(PipelineStage.Authenticate); to make it (and everything before it) run earlier in the ASP.NET pipeline. User is null when you run during the Authenticate stage.

Resources