I am trying to build a method which validates my tokens. I am retrieving my tokens from Azure Active Directory with Open Id Connect Authorization Code Flow. The tokens that I get are the access_token and the id_token. I am using .NET Core.
My validation code is as follows:
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
var handler = new JwtSecurityTokenHandler();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
try
{
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuers = new [] { "https://login.microsoftonline.com/tenantid/v2.0" },
ValidAudiences = new [] { "client-Id" },
ValidateAudience = true,
ValidateIssuer = true,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken = null;
tokenHandler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
return validatedToken != null;
}
catch (SecurityTokenInvalidSignatureException ex)
{
return false;
}
catch(SecurityTokenValidationException)
{
return false;
}
The code below works for the id_token BUT
does not work for the access_token
The error message which I am getting when this method is executed for the access_token is:
IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: CtAAALb-8NsDe333734859crfOc
'.
kid: 'CtAAALb-8NsDe333734859crfOc'.
Exceptions caught:
' '
the nonce header has to be SHA2 hashed before signature verification
Here is an example of code where you can see
jsonToken.Header.Add("nonce", hashedNonce);
private static bool _hashNonceBeforeValidateToken = true;
private const string MicrosoftGraphApplicationId = "00000003-0000-0000-c000-000000000000";
private const string MicrosoftIssuer = "https://sts.windows.net";
public static bool ValidateTokenSignature(string accessToken, ApplicationConfiguration applicationConfiguration) {
var tokenHandler = new JwtSecurityTokenHandler();
var jsonToken = tokenHandler.ReadJwtToken(accessToken);
string[] parts = accessToken.Split('.');
string header = parts[0];
string payload = parts[1];
string signature = parts[2];
//hash nonce and update header with the hash before validating
if (_hashNonceBeforeValidateToken &&
jsonToken.Header.TryGetValue("nonce", out object nonceAsObject))
{
string plainNonce = nonceAsObject.ToString();
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashedNonceAsBytes = sha256.ComputeHash(
System.Text.Encoding.UTF8.GetBytes(plainNonce));
string hashedNonce = Base64Url.Encode(hashedNonceAsBytes);
jsonToken.Header.Remove("nonce");
jsonToken.Header.Add("nonce", hashedNonce);
header = tokenHandler.WriteToken(jsonToken).Split('.')[0];
accessToken = $"{header}.{payload}.{signature}";
}
}
//get the Microsoft JWT signature public key
string stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/v2.0/.well-known/openid-configuration";
if (jsonToken.Header.TryGetValue("ver", out object version) && version.ToString() == "1.0")
{
stsDiscoveryEndpoint = $"https://login.microsoftonline.com/{applicationConfiguration.TenantId}/.well-known/openid-configuration";
}
var openidConfigManaged = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
var configTask = openidConfigManaged.GetConfigurationAsync();
configTask.Wait();
var config = configTask.Result;
var parameteres = new TokenValidationParameters()
{
RequireAudience = true,
ValidateAudience = true,
ValidAudiences = new[] { applicationConfiguration.ApplicationId, MicrosoftGraphApplicationId },
ValidateIssuer = true,
ValidIssuers = new string[] { $"{MicrosoftIssuer}/{applicationConfiguration.TenantId}/", config.Issuer },
IssuerSigningKeys = config.SigningKeys,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
ValidateLifetime = true,
};
var claimPrincipal = tokenHandler.ValidateToken(
accessToken, parameteres, out SecurityToken validatedToken);
return claimPrincipal.Identity.IsAuthenticated;
}
Is the access_token audience your API or Microsoft Graph/other 3rd party service? It only makes sense to validate the tokens that you (your service) consumes, other audiences will take care of this on their own. On top of that, the signature of that JWT may be opaque to you.
See this for more - https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/812#issuecomment-456700813
Related
We are implementing Azure SSO in Traditional ASP.Net Web Application and we want to implement Authorization Code Flow for generating Refresh, Access and Id Tokens.
We have implemented the below code in AuthorizationCodeReceived function of the owin's app.UseOpenIdConnectAuthentication class. From the below mentioned code we are able to successfully fetch the Refreshtoken, AccessToken and IdToken.
But notification.AuthenticationTicket is null and it throws null reference excpetion so we are not able to add the claims for id and access tokens.
Also in the aspx.cs file the HttpContext.Current.User.Identity.IsAuthenticated is returned as false even after generating all the 3 tokens.
Please suggest why notification.AuthenticationTicket is null inside AuthorizationCodeReceived event and what changes we have to do inside AuthorizationCodeReceived event to make HttpContext.Current.User.Identity.IsAuthenticated as "true".
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator dd = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectProtocolValidator();
dd.RequireNonce = false;
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
ClientSecret = clientSecret,
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Scope = "openid profile email offline_access",
ResponseType = OpenIdConnectResponseType.Code,
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) =>
{
return System.Threading.Tasks.Task.FromResult(0);
},
AuthorizationCodeReceived = async notification =>
{
using (var client = new HttpClient())
{
var configuration = await notification.Options.ConfigurationManager.GetConfigurationAsync(notification.Request.CallCancelled);
var request = new HttpRequestMessage(HttpMethod.Post, configuration.TokenEndpoint);
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
[OpenIdConnectParameterNames.ClientId] = notification.Options.ClientId,
[OpenIdConnectParameterNames.ClientSecret] = notification.Options.ClientSecret,
[OpenIdConnectParameterNames.Code] = notification.ProtocolMessage.Code,
[OpenIdConnectParameterNames.GrantType] = "authorization_code",
[OpenIdConnectParameterNames.RedirectUri] = notification.Options.RedirectUri
});
var response = await client.SendAsync(request, notification.Request.CallCancelled);
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.AccessToken,
value: payload.Value<string>(OpenIdConnectParameterNames.AccessToken)));
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.IdToken,
value: payload.Value<string>(OpenIdConnectParameterNames.IdToken)));
}
},
// Attach the id_token stored in the authentication cookie to the logout request.
RedirectToIdentityProvider = notification =>
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var token = notification.OwinContext.Authentication.User?.FindFirst(OpenIdConnectParameterNames.IdToken);
if (token != null)
{
notification.ProtocolMessage.IdTokenHint = token.Value;
}
}
return Task.CompletedTask;
},
SecurityTokenValidated = (context) =>
{ if (context != null)
{
if (context.ProtocolMessage != null && !string.IsNullOrEmpty(context.ProtocolMessage.IdToken))
{
context.AuthenticationTicket.Identity.AddClaim(new Claim("IdToken", context.ProtocolMessage.IdToken));
}
}
return Task.FromResult(0);
}
}
}
);
i am following this tutorial https://medium.com/#far3ns/kong-oauth-2-0-plugin-38faf938a468 and when i request the tokens with
Headers: Content-Type:application/json
Host:api.ct.id
Body:
{
“client_id”: “CLIENT_ID_11”,
“client_secret”: “CLIENT_SECRET_11”,
“grant_type”: “password”,
“provision_key”: “kl3bUfe32WBcppmYFr1aZtXxzrBTL18l”,
“authenticated_userid”: “oneone#gmail.com”,
“scope”: “read”
}
it returns
{
"error_description": "Invalid client authentication",
"error": "invalid_client"
}
no matter what i tried i couldn't fix it, any idea how to make it work properly
You need to create kong developer and it will give you client_id and client_secret_Id. Use those values in generating auth token.
Here is the working c# code.
Option 1
public static string GetOAuthToken(string url, string clientId, string clientSecret, string scope = "all", string grantType = "client_credentials")
{
try
{
string token = "";
if (string.IsNullOrWhiteSpace(url)) throw new ArgumentException("message", nameof(url));
if (string.IsNullOrWhiteSpace(clientId)) throw new ArgumentNullException("message", nameof(clientId));
if (string.IsNullOrWhiteSpace(clientSecret)) throw new ArgumentNullException("message", nameof(clientSecret));
var oAuthClient = new RestClient(new Uri(url));
var request = new RestRequest("Authenticate", Method.POST);
request.AddHeader("Content-Type", "application/json");
var credentials = new
{
grant_type = grantType,
scope = scope,
client_id = clientId,
client_secret = clientSecret
};
request.AddJsonBody(credentials);
var response = oAuthClient?.Execute(request);
var content = response?.Content;
if (string.IsNullOrWhiteSpace(content)) throw new ArgumentNullException("message", nameof(clientSecret));
token = content?.Trim('"');
return token;
}
catch (Exception ex)
{
throw new Exception(ex.Message,ex);
}
}
Option 2
var httpClient = new HttpClient()
var creds = $"client_id={client_id}&client_secret{client_secret}&grant_type=client_credentials";
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var content = new StringContent(creds, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = httpClient.PostAsync("https://myorg/oauth/oauth2/cached/token", content).Result;
var OAuthBearerToken = response.Content.ReadAsStringAsync().Result;
We are using OpenIdConnect based authentication in the asp.net mvc application. Initial login is working fine. But when we use the Ajax call to invoke the action method, User is coming as not authenticated. I checked in Custom Authorization - HttpContext.Request.IsAuthenticated is coming as false.
I checked the cookie ".AspNet.Cookies" and it has the value. Why is open ID not authenticating the user.
Below is my authentication code
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = AuthenticationConfig.ClientId,
Authority = AuthenticationConfig.AADInstance + AuthenticationConfig.TenantId,
PostLogoutRedirectUri = AuthenticationConfig.PostLogoutRedirectURI,
RedirectUri = AuthenticationConfig.RedirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.Code,
SaveTokens = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(AuthenticationConfig.ClientSecret)),
ValidateIssuer = true,
ValidIssuer = AuthenticationConfig.AADInstance + AuthenticationConfig.TenantId + "/v2.0",
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// when an auth code is received...
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(n.AuthenticationTicket.Identity);
//var claimsIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity;
var user = nid.Claims.Where(r => r.Type == PreferedUserNameClaimType).Select(v => v.Value).FirstOrDefault();
var userRolesroles = GetRolesForUser(user);
//nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
userRolesroles.ToList().ForEach(ui => nid.AddClaim(new Claim(ClaimTypes.Role, ui)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
UserService.SetUserInformation(user);
},
RedirectToIdentityProvider = ctx =>
{
bool isAjaxRequest = (ctx.Request.Headers != null && ctx.Request.Headers["X-Requested-With"] == "XMLHttpRequest");
if (ctx.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
if (isAjaxRequest && ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
ctx.Response.Headers.Remove("Set-Cookie");
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
ctx.HandleResponse();
return Task.FromResult(0);
}
}
return Task.FromResult(0);
}
}
});
}
Usually in asp.net, the ApiControllers has no concept of your Controller's authentication. Depending on the way things are builr, you need to add an Authorization header with a bearer access token to let the API know about the authenticated user.
I have integrated Azure AD with my application. Now, I want to use the following code to validate token. But, when I validate Microsoft graph token, I get an error : IDX10501: Signature validation failed. Unable to match key.
My code
public JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = false
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}
To resolve this issue, make sure that the algorithm for the JWT matches with the configuration of your middleware.
Refer to this article and change configurationManager as below:
var issuer = "https://https://login.microsoftonline.com/common/v2.0";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
issuer + "/.well-known/oauth-authorization-server",
new OpenIdConnectConfigurationRetriever());
Please, help!!!!
I am trying to follow a Hybrid implementation (Azure AD + Identity Server 3) from here
I am able to get to the AAD, I seem to get authenticated (get user info, etc) and receive a context.code:
When I pass that code into RequestAuthorizationCodeAsync I get an "invalid_grant" and if I look at the client, here is what I see (Authorization Code is too long):
Here is my code:
public class Startup
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = "https://localhost:44300/",
PostLogoutRedirectUri = "https://localhost:44300/",
ResponseType = "code id_token",
Scope = "openid profile read write offline_access",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async context =>
{
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("oid").Value;
string tenantID = context.AuthenticationTicket.Identity.FindFirst("tid").Value;
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
Uri redirectUri = new Uri(context.Request.Uri.GetLeftPart(UriPartial.Path));
string authorizationCode = context.Code;
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
Constants.TokenEndpoint,
clientId,
"secret", AuthenticationStyle.PostValues);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
authorizationCode, context.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(Constants.UserInfoEndpoint),
tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", context.AuthenticationTicket.Identity.FindFirst("sid").Value));
context.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
context.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = context =>
{
// if signing out, add the id_token_hint
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = context.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
}
}