I have a webapi which is authenticated using JWT tokens.
I validate using a custom JWT protection. This is as follows;
private const string AudiencePropertyKey = "as:client_id";
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
Client client = null;
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include the client_id");
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(audienceId);
}
if (client == null) throw new InvalidOperationException("ClientId does not exist.");
string symmetricKeyAsBase64 = client.Secret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
Access is controlled through a database table, so the user sends across their clientid as a part of the auth service. they are rejected if the clientid doesn't exist in the database, and the ticket is encoded using the secret associated with this db entry and returned to them.
Now I am struggling with the decoding of the JWT on data requests? Now the JWT decodes just fine on jwt.io so I assume there must be someway of decoding using the JwtProtect without requiring a store on the currently dished out JWT tokens? As far as I can see the JwtProtect wants to have the Allowed audiences passed across? (I could so this by returning all from the db but is it really necessary?).
JWT token is just a base64 string, you can freely decode it in multiple ways.
If you want to "unprotect" and validate the ticket you can use System.IdentityModel.Tokens.SecurityTokenHandler.
Check this answer for an example.
BTW, just a personal consideration: the first rule in security is "do not make your own security, but stick with the mass". You will find that following a clear and used path will offer you more support and you will be sure not to mess or to forget something important.
Related
We have a security requirement that we must validate the id token we receive from Azure AD B2C. We need to validate these at the minimum
customSecurityLevel, audience, not before and "expiration time", issuer, nonce
Looking asp.net MVC OWIN middleware, I noticed that that OpenIdConnectAuthenicationOptions provides these:
return new OpenIdConnectAuthenticationOptions
{
...
Notifications = new OpenIdConnectAuthenticationNotifications //Specifies events which the OpenIdConnectAuthenticationMiddleware invokes to enable developer control over the authentication process.
{
AuthenticationFailed = this.AuthenticationFailed,
RedirectToIdentityProvider = this.RedirectToIdentityProvider,
AuthorizationCodeReceived = this.OnAuthorizationCodeReceived,
},
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true, // Important to save the token in boostrapcontext
ValidateAudience = true, // Validate the Audience
ValidateIssuer = true, // Validate the Issuer
ValidateLifetime = true, // Validate the tokens lifetime
ValidIssuer = Issuer, // The issuer to be validated
ValidAudience = ClientId, // The Audience to be validated
},
};
Being quiet new to OWIN, I'm trying to understand below:
Does OWIN middleware magically validates the token we receive from Azure AD B2C or do we need to manually perform validation per this:
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapi-manual-jwt-validation/
At what point in time should token validation should occur i.e. AuthorizationCodeReceived event or on redirect controller/action (page) that's configured on Azure AD B2C redirect URL?
We need to validate more attributes that TokenValidationParameters supports e.g. customSecurityAttribute we send on initial payload. Is there a way to extend this?
How do we parse the token that we receive from Azure AD B2C using OWIN?
Any code sample would be handy.
TO make your question simpler.
The idea behind token is to parse the token and get 3 parts from the token
-Header : contain information about in which algorithm the token haven been encrypted
-Payload : information about the user
-Signature: it's the calculation of encryption of ( Header + Payload) using the Azure certificate or( your identity provider).
Next step the user sends request to your back-end along with JWT.
your back-end will parse the token and get certificate type then will preform HTTP request to your identity provider to get certificate
Next your back-end will construct the certificate option and try to do encryption for ( header + Payload) came from your token the output string must be exactly same Signature you received in the token from your front-end.
If every thing is okay
now your back-end will start validating other attributes like Audience, Issuer
if you configure your token to validate the Audience means your front-end required to provide token contain Audience(Application ID) exactly same your back-end as well as for issuer.
the question now how my back-end knows about certificate?
Azure AD using OpenID connect, More information here
since you configred you tenant in backend the auth packge will make a call to https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration to get the details about your identity provider
and one important link as will is ("jwks_uri": "https://login.microsoftonline.com/common/discovery/keys") where the signature hosted.
You can read and search more how to validate certificate JWT and check this
https://codereview.stackexchange.com/questions/70005/authentication-with-jwt
moving to part 2 of validate more attributes.
Since you are using OpenIdConnect, the package has class called OpenIdConnectEvents that you can trigger events and do what ever you want like this
.AddOpenIdConnect(o =>
{
//Additional config snipped
o.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
//Get user's immutable object id from claims that came from Azure AD
string oid = ctx.Principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
//Get EF context
var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>();
//Check is user a super admin
bool isSuperAdmin = await db.SuperAdmins.AnyAsync(a => a.ObjectId == oid);
if (isSuperAdmin)
{
//Add claim if they are
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "superadmin")
};
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
}
};
});
moving to part 3
parsing token in javascript
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
};
parsing token in C#
use this libary https://www.jsonwebtoken.io/
try {
string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey);
Console.WriteLine(jsonPayload);
} catch (JWT.SignatureVerificationException) {
Console.WriteLine("Invalid token!");
}
or manual
var jwtHandler = new JwtSecurityTokenHandler();
var jwtInput = txtJwtIn.Text;
//Check if readable token (string is in a JWT format)
var readableToken = jwtHandler.CanReadToken(jwtInput);
if(readableToken != true)
{
txtJwtOut.Text = "The token doesn't seem to be in a proper JWT format.";
}
if(readableToken == true)
{
var token = jwtHandler.ReadJwtToken(jwtInput);
//Extract the headers of the JWT
var headers = token.Header;
var jwtHeader = "{";
foreach(var h in headers)
{
jwtHeader += '"' + h.Key + "\":\"" + h.Value + "\",";
}
jwtHeader += "}";
txtJwtOut.Text = "Header:\r\n" + JToken.Parse(jwtHeader).ToString(Formatting.Indented);
//Extract the payload of the JWT
var claims = token.Claims;
var jwtPayload = "{";
foreach(Claim c in claims)
{
jwtPayload += '"' + c.Type + "\":\"" + c.Value + "\",";
}
jwtPayload += "}";
txtJwtOut.Text += "\r\nPayload:\r\n" + JToken.Parse(jwtPayload).ToString(Formatting.Indented);
}
I hope that answer your qustions
I'm trying to find out which is the right way to secure my web api(I am using Sql as database).
Step one: client makes a Login in application.
Client sends username and password.
Asp net checks in sql database if username and password exists.
If exist it sends back a token-key.
In next client's request, do i send again username and password? Or only token?
Also how can i retreive token from asp net and store it inside my asp net application?
Do i need to create a list Collection and add inside the token?
But this way is not thread safe.... Is there any other mechanicm? For stroring-retreiving tokens and other data from asp net application?
You should use JWT Tokens
Here is a useful link for that.
JWT Authentication for Asp.Net Web Api
E.g
Here is how you generate JWT Token
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
I'm trying to get calendars from Office 365 to use them in a REST API (WEB API 2).
I already tried a lot of stuff to generate the JWT, but for each try, I get another error.
My application is properly registred in Azure AAD, the public key is uploaded.
The last thing I tried was from this article : https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/
In his example, I can generate the JWT from two differents ways, but I get the error : x-ms-diagnostics: 2000003;reason="The audience claim value is invalid 'https://outlook.office365.com'.";error_category="invalid_resource"
Here is my code :
`
string tenantId = ConfigurationManager.AppSettings.Get("ida:TenantId");
/**
* use the tenant specific endpoint for requesting the app-only access token
*/
string tokenIssueEndpoint = "https://login.windows.net/" + tenantId + "/oauth2/authorize";
string clientId = ConfigurationManager.AppSettings.Get("ida:ClientId");
/**
* sign the assertion with the private key
*/
String certPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/cert.pfx");
X509Certificate2 cert = new X509Certificate2(
certPath,
"lol",
X509KeyStorageFlags.MachineKeySet);
/**
* Example building assertion using Json Tokenhandler.
* Sort of cheating, but just if someone wonders ... there are always more ways to do something :-)
*/
Dictionary<string, string> claims = new Dictionary<string, string>()
{
{ "sub", clientId },
{ "jti", Guid.NewGuid().ToString() },
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
X509SigningCredentials signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);
JwtSecurityToken selfSignedToken = new JwtSecurityToken(
clientId,
tokenIssueEndpoint,
claims.Select(c => new Claim(c.Key, c.Value)),
DateTime.UtcNow,
DateTime.UtcNow.Add(TimeSpan.FromMinutes(15)),
signingCredentials);
string signedAssertion = tokenHandler.WriteToken(selfSignedToken);
//---- End example with Json Tokenhandler... now to the fun part doing it all ourselves ...
/**
* Example building assertion from scratch with Crypto APIs
*/
JObject clientAssertion = new JObject();
clientAssertion.Add("aud", "https://outlook.office365.com");
clientAssertion.Add("iss", clientId);
clientAssertion.Add("sub", clientId);
clientAssertion.Add("jti", Guid.NewGuid().ToString());
clientAssertion.Add("scp", "Calendars.Read");
clientAssertion.Add("nbf", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(-5)));
clientAssertion.Add("exp", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(15)));
string assertionPayload = clientAssertion.ToString(Newtonsoft.Json.Formatting.None);
X509AsymmetricSecurityKey x509Key = new X509AsymmetricSecurityKey(cert);
RSACryptoServiceProvider rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSACryptoServiceProvider;
RSACryptoServiceProvider newRsa = GetCryptoProviderForSha256(rsa);
SHA256Cng sha = new SHA256Cng();
JObject header = new JObject(new JProperty("alg", "RS256"));
string thumbprint = WebConvert.Base64UrlEncoded(WebConvert.HexStringToBytes(cert.Thumbprint));
header.Add(new JProperty("x5t", thumbprint));
string encodedHeader = WebConvert.Base64UrlEncoded(header.ToString());
string encodedPayload = WebConvert.Base64UrlEncoded(assertionPayload);
string signingInput = String.Concat(encodedHeader, ".", encodedPayload);
byte[] signature = newRsa.SignData(Encoding.UTF8.GetBytes(signingInput), sha);
signedAssertion = string.Format("{0}.{1}.{2}",
encodedHeader,
encodedPayload,
WebConvert.Base64UrlEncoded(signature));
`
My JWT looks like this :
`
{
alg: "RS256",
x5t: "8WkmVEiCU9mHkshRp65lyowGOAk"
}.
{
aud: "https://outlook.office365.com",
iss: "clientId",
sub: "clientId",
jti: "38a34d8a-0764-434f-8e1d-c5774cf37007",
scp: "Calendars.Read",
nbf: 1512977093,
exp: 1512978293
}
`
I put this token in the Authorization header after the "Bearer" string.
Any ideas to solve this kind of issue ? I guess I need a external point of view :)
Thanks
You do not generate the JWT, Azure AD does that.
You would use your certificate to get the access token. Example borrowed from article you linked:
string authority = appConfig.AuthorizationUri.Replace("common", tenantId);
AuthenticationContext authenticationContext = new AuthenticationContext(
authority,
false);
string certfile = Server.MapPath(appConfig.ClientCertificatePfx);
X509Certificate2 cert = new X509Certificate2(
certfile,
appConfig.ClientCertificatePfxPassword, // password for the cert file containing private key
X509KeyStorageFlags.MachineKeySet);
ClientAssertionCertificate cac = new ClientAssertionCertificate(
appConfig.ClientId, cert);
var authenticationResult = await authenticationContext.AcquireTokenAsync(
resource, // always https://outlook.office365.com for Mail, Calendar, Contacts API
cac);
return authenticationResult.AccessToken;
The resulting access token can then be attached to the request to the API.
The reason it does not work is that the Outlook API does not consider you a valid token issuer. It will only accept tokens signed with Azure AD's private key. Which you obviously do not have.
The private key from the key pair you generated can only be used to authenticate your app to Azure AD.
Thanks juunas !
This is the working code :
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/tenantId");
string tenantId = ConfigurationManager.AppSettings.Get("ida:TenantId");
string clientId = ConfigurationManager.AppSettings.Get("ida:ClientId");
String certPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/cert.pfx");
X509Certificate2 cert = new X509Certificate2(
certPath,
"keyPwd",
X509KeyStorageFlags.MachineKeySet);
ClientAssertionCertificate cac = new ClientAssertionCertificate(clientId, cert);
var result = (AuthenticationResult)authContext
.AcquireTokenAsync("https://outlook.office.com", cac)
.Result;
var token = result.AccessToken;
return token;
Other required step for App-only token, you must use the Grant Permissions button in AAD application settings.
I have an ASP.NET Core MVC application allowing anonymous users. This app is calling an ASP.NET Web API that is protected by Identity Server 4. I have created a client in Identity Server describing the MVC app (client) and given it access to the api scope like this:
new Client
{
ClientId = "my-mvc-client-app",
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireConsent = false,
ClientSecrets = new List<Secret> { new Secret("this-is-my-secret".Sha256()) },
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
"my-protected-api"
},
RedirectUris = new List<string>
{
"http://localhost:5009/signin-oidc",
}
}
In my MVC app, I'm using TokenClient to get a token that I can use when making requests to the protected API like this:
var disco = await DiscoveryClient.GetAsync("http://localhost:5010");
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("hrmts-test-candidate-api-scope");
This works fine, but I'm requesting new tokens from Identity Server on every request, which is probably not a good idea.
What is the best practice for handling the tokens? How can I persist them on the client (the MVC app) and how can I handle refresh tokens to make sure the client gets a new token when necessary?
You need to wrap that client in a managed service of some kind (as a singleton) so that you can use it anywhere you need. We have a token component that we use for server to server communication that follows this flow:
public class ServerTokenComponent
{
private TokenResponse Token { get; set; }
private DateTime ExpiryTime { get; set; }
public async Task<TokenResponse> GetToken()
{
//use token if it exists and is still fresh
if (Token != null && ExpiryTime > DateTime.UtcNow)
{
return Token;
}
//else get a new token
var client = new TokenClient("myidpauthority.com","theclientId","thesecret")
var scopes = "for bar baz";
var tokenResponse = await client.RequestClientCredentialsAsync(scopes);
if (tokenResponse.IsError || tokenResponse.IsHttpError)
{
throw new SecurityTokenException("Could not retrieve token.");
}
//set Token to the new token and set the expiry time to the new expiry time
Token = tokenResponse;
ExpiryTime = DateTime.UtcNow.AddSeconds(Token.ExpiresIn);
//return fresh token
return Token;
}
}
In other words - you need to cache that token somehow. When you request the token, you get an ExpiresIn in the response - this will tell you how long the token will be valid.
Another option is to wait until the API returns a 401 - and then request a new token.
Refresh tokens are not used with client credentials flow.
I have a signalR server and I need to validate an OAuth Token that the client will get from Azure AD. I want to do it in the AuthorizeHubConnection method.
I tried this http://geekswithblogs.net/shaunxu/archive/2014/05/27.aspx which basically does this:
var d
dataProtectionProvider = new DpapiDataProtectionProvider();
var secureDataFormat = new TicketDataFormat(dataProtectionProvider.Create());
// authenticate by using bearer token in query string
var token = request.QueryString.Get(WebApiConfig.AuthenticationType);
var ticket = secureDataFormat.Unprotect(token);
This will always return null in the ticket.
After a bit of searching I came across this article: http://ronaldwildenberg.com/signalr-hub-authentication-with-adal-js-part-2/
Here is what it does:
public class JwtTokenAuthorizeAttribute : AuthorizeAttribute
{
// Location of the federation metadata document for our tenant.
private const string SecurityTokenServiceAddressFormat =
"https://login.windows.net/{0}/federationmetadata/2007-06/federationmetadata.xml";
private static readonly string Tenant = "yourtenant.onmicrosoft.com";
private static readonly string ClientId = "12345678-ABCD-EFAB-1234-ABCDEF123456";
private static readonly string MetadataEndpoint = string.Format(
CultureInfo.InvariantCulture, SecurityTokenServiceAddressFormat, Tenant);
private static readonly IIssuerSecurityTokenProvider CachingSecurityTokenProvider =
new WsFedCachingSecurityTokenProvider(
metadataEndpoint: MetadataEndpoint,
backchannelCertificateValidator: null,
backchannelTimeout: TimeSpan.FromMinutes(1),
backchannelHttpHandler: null);
public override bool AuthorizeHubConnection(
HubDescriptor hubDescriptor, IRequest request)
{
// Extract JWT token from query string (which we already did).
...
// Validate JWT token.
var tokenValidationParameters =
new TokenValidationParameters { ValidAudience = ClientId };
var jwtFormat =
new JwtFormat(tokenValidationParameters, CachingSecurityTokenProvider);
var authenticationTicket = jwtFormat.Unprotect(userJwtToken);
...
The problem with this is that it proposes to copy classes from the Katana project: https://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.ActiveDirectory/WsFedCachingSecurityTokenProvider.cs.
This looks super ugly. Another problem is that I don't know the tenant id and I couldn't find it anywhere with the token. So even if this works, I would be one step away.
To wrap it up: I want to find a way to validate the AzureAD token with SignalR. It looked like a simple thing in the beginning. Is there a simpe way for this?
It was simple:
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(token, authTokenValidationParameters, out validatedToken);