Validating OpenId IdToken On Each Request - c#

I'm trying to validate the IdToken I get from Azure AD on each request but I keep getting an error saying that there is no signature on the token. When I validate the Access Token it works, but I would rather use the Id Token as that contains the users claims. Is there anyway to make Azure send back the Id Token with the signature as well?
public JwtSecurityToken ValidateJwtToken(string jwtToken)
{
string stsDiscoveryEndpoint = $"{_authority}/.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,
ValidateLifetime = false,
IssuerSigningKeys = config.SigningKeys
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
try
{
SecurityToken token;
tokendHandler.ValidateToken(jwtToken, validationParameters, out token);
return token as JwtSecurityToken;
}
catch (Exception ex)
{
loggingService.LogError("Could not validate azure ad token", nameof(AzureSecurityService), ex);
return null;
}
}
public async Task<string> GenerateToken(string code)
{
AuthenticationContext authenticationContext = new AuthenticationContext(_authority);
try
{
string baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
AuthenticationResult result =
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(code, new Uri(baseUrl), new ClientCredential(_clientId, _clientSecret));
return result.IdToken;
}
catch (AdalException adalex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), adalex);
return null;
}
catch (Exception ex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), ex);
return null;
}
}
public async Task<string> GetAuthUrl()
{
AuthenticationContext authenticationContext = new AuthenticationContext(_authority);
// Config for OAuth client credentials
try
{
string baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
var authUri =
await authenticationContext.GetAuthorizationRequestUrlAsync("00000000-0000-0000-0000-000000000000", _clientId, new Uri(baseUrl), UserIdentifier.AnyUser, null);
return authUri.ToString();
}
catch (AdalException adalex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), adalex);
return null;
}
catch (Exception ex)
{
loggingService.LogError("Could not get authorization request url", nameof(AzureSecurityService), ex);
return null;
}
}
UPDATE
So for whatever reason even though there was no scope being provided I was getting back claims. What I have done now is just add query parameters to the auth url for scope and request both openid and profile:
var authUri =
await authenticationContext.GetAuthorizationRequestUrlAsync("00000000-0000-0000-0000-000000000000", _clientId, new Uri(GetBaseUrl()), UserIdentifier.AnyUser, "scope=openid profile");
My question is now why does the default scope not return an id_token with a signature?

You should have forgotten to put required scope value in your authorization request,
From OpenID Connect specification
Scope
REQUIRED. OpenID Connect requests MUST contain the openid scope value.
If the openid scope value is not present, the behavior is entirely
unspecified. Other scope values MAY be present. Scope values used that
are not understood by an implementation SHOULD be ignored. See
Sections 5.4 and 11 for additional scope values defined by this
specification.
So you must specify openid in the authorization request.
And Azure does return an id token for OAuth 2.0 token response (when openid scope is not present).
From Azure AD OAuth2.0 documentation,
id_token
An unsigned JSON Web Token (JWT). The app can base64Url decode the
segments of this token to request information about the user who
signed in. The app can cache the values and display them, but it
should not rely on them for any authorization or security boundaries.
As the documentation say, it's just there to display the end user. One must not use this id token for authenticate/authorize.
On the other hand, for a proper OpenID Connect token response, Auzre sends you a signed id token,
From documentation
id_token
The id_token that the app requested. You can use the id_token to
verify the user's identity and begin a session with the user.
Validate the id_token section of the same documentation explains how to validate the token
Hope this solved your issue.

Related

Authenticate and authorize using OAuth 2.0 using Microsoft.identity.Ciient in razor pages

I want to implement authentication and authorization using Microsoft.Identity.Client in razor pages
I want to sign In users and logout also with the help of Microsoft office 365
For login I am getting access toke from code and authorizing as shown below
public async Task<IActionResult> OnGetAsync(string code)
{
string clientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string tenantId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string clientSecret = "XXXXXXXXXXXXXXXXXX";
var httpClient = new HttpClient();
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.WithRedirectUri("http://localhost:5000/login")
.Build();
var scopes = new string[]
{
"User.Read",
"email",
"offline_access",
"openid",
"profile"
};
try
{
AuthenticationResult result = await app
.AcquireTokenByAuthorizationCode(scopes, code)
.ExecuteAsync();
string identifier = result.Account.HomeAccountId.Identifier;
Email = result.Account.Username;
accesstoken = result.AccessToken;
Console.WriteLine(result.UniqueId);
var graphQlInformation = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/beta/me");
graphQlInformation.Headers.Add("Authorization", "Bearer " + accesstoken);
var information = httpClient.SendAsync(graphQlInformation).Result;
var info = information.Content.ReadAsStringAsync().Result;
JObject json = JObject.Parse(info);
if (information.IsSuccessStatusCode)
{
Console.WriteLine("authorized");
Name = json["mailNickname"].ToString();
Console.WriteLine(json["mailNickname"].ToString());
}
else
{
Console.WriteLine("Un authorized");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return Page();
}
Wit this, I can able to get access token, but I want to keep user session like a cookie, means once a user is signed in,
if he closes browser and open it again, he shouldn't be asked to sign In again
I am following this article until now for authentication and authorization and change some implementation
So, I have 2 questions here
how can I sign in like a session with Oauth 2.0
Thw way I am following for authentication and authorization is right or not.
Please help, and please ask if any more clarification is needed, thanks

ASP.NET Web API 2 - Getting token using on-behalf-of flow

I'm currently trying to find a way to access a web api thru web api using on-behalf-of flow of MSAL.NET
So far, I use the token in the Authorization header to generate the token to be used for the third party group's API
but I'm hoping of finding alternative to this since getting the access token from the header is I think not a healthy approach.
I hope somebody could help me of getting a fresh token that I can use to generate another token with on-behalf-of flow
this is the code that I have:
var cba = ConfidentialClientApplicationBuilder.Create("<our azure client ID>")
.WithAuthority(new Uri("https://login.microsoftonline.com/<Tenant ID>/oauth2/v2.0"))
.WithClientSecret("<Our azure client secret>")
.WithTenantId("<Tenant ID>")
.Build();
AuthenticationResult result = null;
try
{
var authToken = HttpContext.Current.Request.Headers["Authorization"];
var user = new UserAssertion(authToken.Split(' ')[1]);
var scopes = new string[] { "<third party resource client id>/1234.ThirdParty.Api" };
result = await cba.AcquireTokenOnBehalfOf(scopes,user).ExecuteAsync().ConfigureAwait(false);
return new Jwt
{
AccessToken = result.AccessToken,
TokenType = "Bearer",
RefreshToken = null,
ExpiresIn = 3600
};
}
catch (MsalUiRequiredException ex)
{
throw ex;
}
catch (MsalClientException ex)
{
throw ex;
}

Use JWT with OAuth Authentication in .Net-Core

I have a custom implementation of .AddOAuth() in .Net-Core. I've created a nuget package for Authentication using Coinbase (which is basically a clone of the add google implementation plus a few custom options specific to coinbase) full source. I've looked at a few other questions on this however they don't seem to implement OAuth (e.g I cannot pass scopes) I would like to login using OAuth But I want to return to my clients a JWT.
When I try to use JWT with AddCoinbase ( which is just a derrivative of AddOAuth)
services.AddAuthentication(JWT_BEARER_AUTH)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
//TODO: get key from secret section
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
})
.AddCoinbase(options => {
options.AccessAllAccounts = true;
options.SendLimitAmount = 1;
options.SendLimitCurrency = "USD";
options.SendLimitPeriod = SendLimitPeriod.day;
options.ClientId = Configuration["Coinbase:ClientId"];
options.ClientSecret = Configuration["Coinbase:ClientSecret"];
COINBASE_SCOPES.ForEach(scope => options.Scope.Add(scope));
options.SaveTokens = true;
options.ClaimActions.MapJsonKey("urn:coinbase:avatar", "avatar_url");
});
After I login to coinbase the external callback redirects me
[HttpGet("ExternalLoginCallback")]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
//TODO: Handle remote error failure
throw new Exception($"Error from external provider: {remoteError}");
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
//TODO: Handle null external login info
throw new Exception("Error: could not find user info");
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);1
var user = await (result.Succeeded ?
_userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey)
: this.CreateIdentityUser(info));
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return Redirect(returnUrl);
}
After the redirect I never receive a JSON Web Token I always receive a Cookie. How can I leverage OAuth Authentication while serving JWT to my Clients?
OAuth is not a Json Web Token solution. OAuth 2.0 provides authorization and optionally identification (OIDC).
When you authorize via an OAuth 2.0 endpoint, you receive an Access Token and optionally an ID Token. The ID Token is a Signed JWT. The Access Token is an opaque object that is a Signed JWT for some vendor implementations but not all (Google is opaque).
After authorization you receive one or two tokens (access and ID). You can wrap them in your own JWT, sign it and then use the combined JWT any way that you want.

Get accesstoken from azure in web with openidconnect

I have a desktop app where I authenticate users via Azure via AuthenticationContext.AcquireTokenAsync.
With the result from this method I can get the access-token, send it to my WCF and in my WCF use JwtSecurityToken / ConfigurationManager< OpenIdConnectConfiguration > to validate the token.
I've implemented login via Azure in a web app now by configuring it with app.UseOpenIdConnectAuthentication. So in my web app I dont explicitly call a method that returns a token. Rather I jack this in in asp.net's flow.
But now I want to fetch the token in a method and send it for validation similiarly how I did in my desktop app. I cannot find any token that the ConfigurationManager accepts however. I've looked in the regular HttpContext and Owincontext but no info that I find there is useful. Is the accesstoken stored anywhere where I can fetch it? Or do I have to do another request to get an accesstoken?
You should be getting access token as part of the response.
A simple way would be to look at the Authorization header. Look at code below -
HttpContext.Current.Request.Headers["Authorization"];
Also, I don't know what you mean by send the token for validation.
If you're trying to validate the token manually, here's a sample that does exactly that -
Manually validating a JWT access token in a web API
In the sample, specifically look at the Global.asax.cs
string jwtToken = null;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
jwtToken = authHeader.Parameter;
}
if (jwtToken == null)
{
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
return response;
}
.........
.........
.........
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// We accept both the App Id URI and the AppId of this service application
ValidAudiences = new[] { audience, clientId },
// Supports both the Azure AD V1 and V2 endpoint
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
IssuerSigningKeys = signingKeys
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);

How to validate the access token I'm receiving from WPF client in Web API?

I'm building two applications, WPF and Web API.
WPF connects to an identity server (now it's Azure AD) and get the access token then send it to my Web API to get the data.
How can I, in Web API, validate the access token to make sure it's correct. ?
Now I'm using Azure as I said but I should build to be able to validate any access token from any identity provider.
Is there an example or article explain this ?
Thanks
Encountered the same probelem.
I decided to use JWTToken
My architecture is the next one
Front <-> WebApi <-> Database
Front is in MVC4 WebApi2
The front will use a FormAuthentication method.
Save the JWT token once the user is fully logged, then send the Authentication Header on each request I do to the webapi.
The front part will only carry the encrypted JWT Token, nothing will be decrypted from it. Only send the token into the authentication http header tag.
On the webapi side each request are cached into DelegatingHandle, Check is the called method need to be authorized or not, validate the JWTToken then do what ever the webapi method does.
I cannot send you some part of my code because this is bellongs now to my company, but I can link you some Internet readings :)
1 - JWT
2- ASP.Net Web API with JWT (webapi handler)
You must then use the [Authorize] or [AllowAnonymous] tags on your webapi methods.
You can even create your own tag to handle all the Groups things.
If you have more questions, feel free to ask :)
I think this will answer to 99% of your security Questions.
I know this is one year old question, but I hope this answer will help other users :)
Client Side code
public async void Authenticate(string aadInstance, string tenant, string clientId, Uri redirectUri, string resourceId)
{
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
authContext = new AuthenticationContext(authority, new FileCache());
AuthenticationResult result = null;
try
{
result = await authContext.AcquireTokenSilentAsync (resourceId, clientId);
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.UserInteractionRequired || ex.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
result = await authContext.AcquireTokenAsync(resourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always));
}
}
ticket = result.AccessToken;
user = result.UserInfo.DisplayableId.Split('#')[0];
}
catch (Exception ex)
{
ticket = "Error";
throw ex;
}
}
Server side code
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
private 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, //.net core calls it "IssuerSigningKeys" and "SigningKeys"
ValidateLifetime = true
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}

Categories