IDX12709: CanReadToken() returned false. JWT is not well formed - Open ID Connect Authentication - asp.net-mvc-5

We are implementing authentication in asp.net mvc application using open id connect.
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = RedirectUri,
ResponseType = OpenIdConnectResponseType.Code,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretkey)),
ValidateIssuer = true,
ValidIssuer = authority,
ValidateAudience = true
// ValidAudience = strAudience
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// when an auth code is received...
AuthorizationCodeReceived = (context) => {
// get the OpenID Connect code passed from Azure AD on successful auth
string code = context.Code;
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(code);
//// successful auth
return Task.FromResult(0);
},
SecurityTokenValidated = (ctx) =>
{
// We can remove claims that are not necessary in this context, mitigating the cookie size.
var identity = ctx.AuthenticationTicket.Identity;
return Task.FromResult(0);
},
AuthenticationFailed = (context) => {
context.HandleResponse();
return Task.FromResult(0);
}
}
});
Authentication is successful, and I am able to get the code.
I am using Authorize attribute in my controller.
Using msal, we received auth token and id_token. I am getting the proper tokens, still after getting tokens, I am getting Infinite loop. I am used UseKentorOwinCookieSaver also. But nothing is working.

The authorization code is not a JWT.
You use it to get JWTs from Azure AD's token endpoint.
You can use MSAL (Microsoft Authentication Library) for this, or make the call yourself.
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow

Related

Where can I find the public key to verify the directline.botframework.com conversation JWT token?

Following the info from Azure Bot Service Authentication I tried to verify the JWT token using the public keys exposed via OpenId:
https://login.botframework.com/v1/.well-known/openidconfiguration
https://login.microsoftonline.com/botframework.com/v2.0/.well-known/openid-configuration
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
but the key from the directline.botframework.com conversation JWT token is in neither of them, see the error below:
"IDX10501: Signature validation failed. Unable to match key: kid: '...."
ConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(openIdMetadataAddress, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConnectConfiguration = await configurationManager.GetConfigurationAsync(CancellationToken.None);
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = authorizationDomain,
ValidateAudience = false,
IssuerSigningKeys = openIdConnectConfiguration.SigningKeys
};
try
{
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
jwtSecurityTokenHandler.ValidateToken(jwt, tokenValidationParameters, out _);
return true;
}
catch (SecurityTokenException)
{
return false;
}
JWT token example (generated when you start a directline conversation in bot framework):
ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImtpZCI6ICJBT08tZXhGd2puR3lDTEJhOTgwVkxOME1tUTgiLA0KICAieDV0IjogIkFPTy1leEZ3am5HeUNMQmE5ODBWTE4wTW1ROCIsDQogICJ0eXAiOiAiSldUIg0KfQ.ew0KICAiYm90IjogImRldi1tYXJpdXNpbXBvLW5lcnRlc3Rib3QwbmVnNC1ib3QiLA0KICAic2l0ZSI6ICJ0RVRMM2ZES3ZGdyIsDQogICJjb252IjogIkZPeXRUdThrTzVRNFVOZmxpS3pSMlgtaCIsDQogICJuYmYiOiAxNTc1MzcxNDYzLA0KICAiZXhwIjogMTU3NTM3NTA2MywNCiAgImlzcyI6ICJodHRwczovL2RpcmVjdGxpbmUuYm90ZnJhbWV3b3JrLmNvbS8iLA0KICAiYXVkIjogImh0dHBzOi8vZGlyZWN0bGluZS5ib3RmcmFtZXdvcmsuY29tLyINCn0.IMKMdlart3nEg6iegVvz5MQ86cp36nLXK1mIT0a7xiOmRLMMlvUjqHA9d2EJUovYAML4RGAapP7BWYgU9CnYtL9dXrJwj_JNacJDov18zUTzbyfzcL8goFJG_PJRjJZbN7ZZZdp1lIis9DbrL56HQBgiBuW4BGhNhgmBauh8SFOIvWfhOYmWoxyfI7Uzkd_5LTVdeL7Lyqi5Ulxzf8UsuDI372US6dA0LZ0BZMCU-M6S9bYFCSBwrvjD5uZOYJ8drCuXnuOl1rxRP_kfMVi-kodWZ84-puo5JYt5QhpptP6vuBYO5-6fW359zJ1csUk-xWFlOH88dh09lpJDbcXgXg
using (var client = new DirectLineClient(secretKey))
{
var conversation = await client.Conversations.StartConversationAsync();
var token = conversation.Token;
}
UPD: I'm not sure what the key from the directline.botframework.com conversation JWT token exactly is. If you can provide expired token for me, it should be possible to find out how to validate it.
Metadata endpoint:
https://login.microsoftonline.com/botframework.com/v2.0/.well-known/openid-configuration
Your code works fine.
Please check out the steps of the test I've done below:
Create a Web App Bot via Azure Portal.
Full description here: https://learn.microsoft.com/en-us/azure/bot-service/abs-quickstart?view=azure-bot-service-4.0
Obtain a token.
Take MICROSOFT-APP-ID and MICROSOFT-APP-PASSWORD from the Configuration of your Web App Bot.
POST https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=MICROSOFT-APP-ID&client_secret=MICROSOFT-APP-PASSWORD&scope=https%3A%2F%2Fapi.botframework.com%2F.default
Come up with values to validate the token.
3.1. Metadata endpoint
Constructed from token endpoint.
https://login.microsoftonline.com/botframework.com/v2.0/.well-known/openid-configuration
3.2. Issuer
Decoded the token at jwt.io and took actual issuer from there.
https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/
3.3. Audience
Same way as for issuer.
https://api.botframework.com
Validate the token and get ClaimsPrincipal object decoded from the token.
static async Task Main(string[] args)
{
var jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyIsImtpZCI6IkJCOENlRlZxeWFHckdOdWVoSklpTDRkZmp6dyJ9.eyJhdWQiOiJodHRwczovL2FwaS5ib3RmcmFtZXdvcmsuY29tIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiLyIsImlhdCI6MTU3NTkyMDQwMSwibmJmIjoxNTc1OTIwNDAxLCJleHAiOjE1NzU5MjQzMDEsImFpbyI6IjQyVmdZRGhjMDMwNGFrdENBcXZMYTM2aFJTTExBUT09IiwiYXBwaWQiOiI0MmY5NGM0MS0wYmMwLTRiN2MtODc2MC1jOGI1NTRhYjE2NDIiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwidGlkIjoiZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiIiwidXRpIjoiMWpvWi1TUng5a1MwdUxucVYyOE5BQSIsInZlciI6IjEuMCJ9.WWxIinArkAJgVyAUMu6UJvCy9OJ-B2KGxpT-t9wdRF9qlpw00GvXXuL0HCpUEIWC0efA3ETF3bBBJVYjcXoKsC6Up2UWzkAgA2O_TZhPkG5Tkm5MT7f_mIdoEVWoddawjv3ec_EUfSq1B_UrQu-05AHMe0n46kN94yUWbsIAv9z6Q_HSuKO6_kSSyGwbnsAbsT2nWqYyE05BstvZUccQrSvR4UdbugKDEDxAixhVvOrFJiLng3pKeSljXUxWte7ETw59X9EuA4WJPURzW-kWPJ8tGIP2Wz6RVDU-D1eCp-DB3o4PxT-t8UTBMjwUJBFqQo-w1GtQasJwcnUKKkBhgA";
var claimsPrincipal = await Authenticate(jwt);
}
public static async Task<ClaimsPrincipal> Authenticate(string jwt)
{
var openIdMetadataAddress = "https://login.microsoftonline.com/botframework.com/v2.0/.well-known/openid-configuration";
var issuer = "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/";
var audience = "https://api.botframework.com";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
openIdMetadataAddress,
new OpenIdConnectConfigurationRetriever());
var openIdConnectConfiguration = await configurationManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
// Updated validation parameters
ValidIssuer = issuer,
ValidAudience = audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openIdConnectConfiguration.SigningKeys
};
try
{
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var claimsPrincipal = jwtSecurityTokenHandler.ValidateToken(jwt, tokenValidationParameters, out _);
return claimsPrincipal;
}
catch (SecurityTokenException e)
{
return null;
}
}

IdentityServer4 signout MVC5 App

following Issue 1 and Issue 2 I cant sign out from IS4. Local ASP.NET cookies are not removed and I cannot re-login to IS4.
It works for Core MVC app, but for MVC5 does not.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "TRUX.NET",
Authority = baseAddress,
RedirectUri = $"http://localhost:2510/signin-oidc",
PostLogoutRedirectUri = $"http://localhost:2510/signout-callback-oidc",
ResponseType = "code id_token",
Scope = "openid profile offline_access custom.profile AuthorizationWebApi Common.WebApi",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
--------
RedirectToIdentityProvider = n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
Server config
new Client
{
ClientId = "MVC50",
ClientName = "MVC50 APP",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets= {new Secret("123456".Sha256()) },
RedirectUris = { "http://localhost:2510/signin-oidc" },
PostLogoutRedirectUris = {"http://localhost:2510/signout-callback-oidc"},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"custom.profile",
"AuthorizationWebApi",
"Common.WebApi"
},
AllowOfflineAccess = true
}
If any one can help, i'll appreciate.
regards.
Update :
I removed signout-callback-oidc from both server and client configs.
My signout action is this one
public async Task<ActionResult> SignOut()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
Undeterministically I can call end session endpoint. The logs are given below.
id_token_hint is not null
|1|Microsoft.AspNetCore.Hosting.Internal.WebHost|INFO|Request starting HTTP/1.1 OPTIONS http://www.abcdef.com:5000/connect/endsession?post_logout_redirect_uri=http%3a%2f%2flocalhost%3a2510%2f&id_token_hint=(hint)
|1|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|DEBUG|OPTIONS requests are not supported
|0|IdentityServer4.CorsPolicyProvider|DEBUG|CORS request made for path: /connect/endsession from origin: http://localhost:2510 but rejected because invalid CORS path
|9|Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware|DEBUG|AuthenticationScheme: Identity.Application was not authenticated.
|0|IdentityServer4.Hosting.EndpointRouter|DEBUG|Request path /connect/endsession matched to endpoint type EndSession
|0|IdentityServer4.Hosting.EndpointRouter|DEBUG|Mapping found for endpoint: EndSession, creating handler: IdentityServer4.Endpoints.EndSessionEndpoint
|0|IdentityServer4.Hosting.IdentityServerMiddleware|INFO|Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionEndpoint for /connect/endsession
|0|IdentityServer4.Endpoints.EndSessionEndpoint|WARN|Invalid HTTP method for end session endpoint.
|0|IdentityServer4.Hosting.IdentityServerMiddleware|TRACE|Invoking result: IdentityServer4.Endpoints.Results.StatusCodeResult
|9|Microsoft.AspNetCore.Server.Kestrel|DEBUG|Connection id "0HL3SPFTNB2FH" completed keep alive response.
|2|Microsoft.AspNetCore.Hosting.Internal.WebHost|INFO|Request finished in 1322.3713ms 405

Azure AD Sign Out

I like to sign out my webapp from an azure ad b2c. I tried the following like suggested in this sample https://www.janaks.com.np/azure-ad-identity-provider-in-aspnet-core-application/.
if (HttpContext.User.Identity.IsAuthenticated)
{
await HttpContext.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
await HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
With the following configuration in the Startup.cs:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = settings.SignInPolicyId,
AutomaticChallenge = true,
CallbackPath = settings.SignInCallbackPath,
ClientId = settings.ClientId,
MetadataAddress = string.Format(settings.AadInstance, settings.Tenant, settings.SignInPolicyId),
PostLogoutRedirectUri = settings.RedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
AutomaticAuthenticate = true,
Scope = { "openid" },
ResponseType = "id_token",
GetClaimsFromUserInfoEndpoint = true
});
But when I try sign out from the webapp following Exception will be thrown:
InvalidOperationException: No authentication handler is configured to handle the scheme: OpenIdConnect
Thanks for your help.
You have to identify the authentication scheme that you set:
if (HttpContext.User.Identity.IsAuthenticated)
{
await HttpContext.Authentication.SignOutAsync(settings.SignInPolicyId);
await HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
You will somehow have to get the policy id to this controller and use it to identify the appropriate middleware.
The accepted answer is good for Auth 1, but in Auth 2 that method is depreciated, so use the extension method.
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
Reference: https://github.com/aspnet/Announcements/issues/232

How to pass data from OWIN UseOpenIdConnectAuthentication, AuthorizationCodeReceived notification back to the controller?

I configured the AppBuilder to use OpenIdConnectAuthentication and I added the [Authorize] header on the endpoint I want the authentication to be applied.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions()
{
ClientId = ClientId,
Authority = Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = async (context) =>
{
var code = context.Code;
await Task.FromResult(0);
}
}
});
How can I send the variable code back to the controller? Here there is no HttpContext, and the OwinContext I have access token is different than the one in the controller.

Unable to authenticate web api with Bearer Token

I'm currently working on a project where I have a Web API that uses the Graph API to create accounts in my Azure AD and put them in the correct group as well.
Next to that I have several API calls exposed that will be called by a native iOS app as well as an angularJS web app. The client has concocted some weird way of doing the authentication because he firmly believes his users to be utter morons.
The client is handing custom prepped iOS devices to certain people. The device has an Azure AD User(principal) and password. Once they are handed out, some process on the angularJS web app does this, the app will then call my token controller that looks like this:
public async Task<string> GetTokenForUserAsync(string userPrincipalName, string password)
{
var uri = string.Format("{0}/oauth2/token", AuthString);
using (var httpClient = new HttpClient
{
DefaultRequestHeaders = { Accept = { new MediaTypeWithQualityHeaderValue("application/json") } }
})
{
var values = new Dictionary<string, string>
{
{"resource", GraphUrl },
{"client_id", ClientId },
{"client_secret", ClientSecret },
{"grant_type", "password" },
{"username", userPrincipalName },
{"password", password },
{"scope", "openid" }
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync(uri, content);
var responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
The passed parameters are, not 100% exact, but very alike:
AuthString : "https://login.microsoftonline.com/myAzureAD.onmicrosoft.com"
GraphUrl : "https://graph.windows.net"
ClientId : My Web API ClientId (a guid)
ClientSecret : My Web API Client Secret (2year valid access key)
So this call actually provides me with an access_token. The problem that I'm having is that the tokens I get are never authorized. I've tried several Startup setups, but none are working.
Currently I've got the following code in my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:ClientId"]
}
});
//app.UseWindowsAzureActiveDirectoryBearerAuthentication(
// new WindowsAzureActiveDirectoryBearerAuthenticationOptions
// {
// TokenValidationParameters = new TokenValidationParameters
// {
// ValidAudience = ConfigurationManager.AppSettings["ida:ClientId"]
// },
// Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
// });
}
It's the first time that I'm working with Azure and Active Directory. So I'm probable doing something really stupid. At this moment in time I don't care much about styling and 'the right way'. I just want this thing to work :,/
Hope I described my problem correctly and documented my question accordingly. If you have any questions, please don't hesitate to ask!
Thanks a bunch in advance
I dont think you can get accesstoken from var values = new Dictionary
{
{"resource", GraphUrl },
{"client_id", ClientId },
{"client_secret", ClientSecret },
{"grant_type", "password" },
{"username", userPrincipalName },
{"password", password },
{"scope", "openid" }
};
so you could try other methods:
AuthenticationContext aContext =
new AuthenticationContext("https://login.microsoftonline.com/tenantid");
AuthenticationResult aResult =
aContext.AcquireToken("https://graph.windows.net",
"1950a258-227b-4e31-a9cf-717495945fc2",
new UserCredential(UserId, PasswordId));
string result = string.Empty;
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", aResult.AccessToken);
HttpResponseMessage response =
httpClient.GetAsync("https://graph.windows.net/tenantid/users/userid?api-version=1.6").Result;
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}
Console.WriteLine(result);
Console.ReadLine();

Resources