I am new to OAuth and I used this tutorial to generate access token from client app to target app. The code itself is working fine, but the access token I generated has invalid signature when I decoded on https://jwt.io/
Here's the code from the tutorial
public class ServicePrincipal
{
/// <summary>
/// The variables below are standard Azure AD terms from our various samples
/// We set these in the Azure Portal for this app for security and to make it easy to change (you can reuse this code in other apps this way)
/// You can name each of these what you want as long as you keep all of this straight
/// </summary>
static string authority = ""; // the AD Authority used for login. For example: https://login.microsoftonline.com/myadnamehere.onmicrosoft.com
static string clientId = ""; // client app's client id
static string clientSecret = ""; // client app's secret key
static string resource = ""; // target app's App ID URL
/// <summary>
/// wrapper that passes the above variables
/// </summary>
/// <returns></returns>
static public async Task<AuthenticationResult> GetS2SAccessTokenForProdMSAAsync()
{
return await GetS2SAccessToken(authority, resource, clientId, clientSecret);
}
static async Task<AuthenticationResult> GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext context = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = await context.AcquireTokenAsync(
resource, // the resource (app) we are going to access with the token
clientCredential); // the client credentials
return authenticationResult;
}
}
There is another piece of code I found that can also generate the access token:
AuthenticationContext authenticationContext =
new AuthenticationContext({authority});
ClientCredential clientCredential = new ClientCredential({client app id}, {client app secret});
try
{
AuthenticationResult result =
await authenticationContext.AcquireTokenAsync({target app's App ID URL},
clientCredential);
}
catch (Exception e)
{
return false;
}
Both of the code gave me invalid signature access token with version 1.0
There are two issues here:
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
Why would it be invalid signature?
I tried the same code with yours, got the same situation invalid signature, but when I changed the jwt ALGORITHM to HS256, I got the Signature Verified.
And the signature:
And the differences about RRS256 and HS256:
RS256 (RSA Signature with SHA-256) is an asymmetric algorithm, and it uses a public/private key pair: the identity provider has a private (secret) key used to generate the signature, and the consumer of the JWT gets a public key to validate the signature. Since the public key, as opposed to the private key, doesn't need to be kept secured, most identity providers make it easily available for consumers to obtain and use (usually through a metadata URL).
HS256 (HMAC with SHA-256), on the other hand, is a symmetric algorithm, with only one (secret) key that is shared between the two parties. Since the same key is used both to generate the signature and to validate it, care must be taken to ensure that the key is not compromised.
Are you posting your key into the form at jwt.io? Try to make a real rest call using the token in the authorization header. If everything is working and jwt isn't, maybe it's on them.
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
You are using OAuth2.0 , the ver:"1.0" means the JWT token is issued by Azure AD V1.0 Endpoint .
Why would it be invalid signature?
The API needs to check if the algorithm, as specified by the JWT header (property alg), matches the one expected by the API . AAD use RS256 , so you should change to RS256:
The normal way is to build from modulus and exponent , finding them from https://login.microsoftonline.com/common/discovery/keys matching kid and x5t from the token . Any use online tool like https://play.golang.org/ to get public key consists of two components: n and e. You can also use x5c value , click here for samples .
Related
I have a Azure SQL Db with encrypted columns (Always Encrypted with Azure KeyVault). I can access this db from SSMS and I can see the decrypted data.
I also have a web app made with .Net Core 5.0 which is deployed to Azure App Service. The app service has Managed Identity turned on and Key Vault that has enc/dec keys for that SQL Db has access policy setting to permit this app service to decrypt the data.
The web app works with managed identity as I can see that not encrypted data is retrieved without any issue.
Also, connection string does include Column Encryption Setting=enabled;. Here's the connection string:
Server=tcp:server.database.windows.net,1433;Database=somedb;Column Encryption Setting=enabled;
The problem is I can't find ANY samples with this kind of set up. I found some and I understand I need to register SqlColumnEncryptionAzureKeyVaultProvider. Here's my code to obtain SqlConnection:
internal static class AzureSqlConnection
{
private static bool _isInitialized;
private static void InitKeyVaultProvider(ILogger logger)
{
/*
* from here - https://github.com/dotnet/SqlClient/blob/master/release-notes/add-ons/AzureKeyVaultProvider/1.2/1.2.0.md
* and - https://github.com/dotnet/SqlClient/blob/master/doc/samples/AzureKeyVaultProviderExample.cs
*
*/
try
{
// Initialize AKV provider
SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider =
new SqlColumnEncryptionAzureKeyVaultProvider(AzureActiveDirectoryAuthenticationCallback);
// Register AKV provider
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(
new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(1, StringComparer.OrdinalIgnoreCase)
{
{SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider}
});
_isInitialized = true;
}
catch (Exception ex)
{
logger.LogError(ex, "Could not register SqlColumnEncryptionAzureKeyVaultProvider");
throw;
}
}
internal static async Task<SqlConnection> GetSqlConnection(string connectionString, ILogger logger)
{
if (!_isInitialized) InitKeyVaultProvider(logger);
try
{
SqlConnection conn = new SqlConnection(connectionString);
/*
* This is Managed Identity (not Always Encrypted)
* https://learn.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi#modify-aspnet-core
*
*/
#if !DEBUG
conn.AccessToken = await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/");
logger.LogInformation($"token: {conn.AccessToken}");
#endif
await conn.OpenAsync();
return conn;
}
catch (Exception ex)
{
logger.LogError(ex, "Could not establish a connection to SQL Server");
throw;
}
}
private static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope)
{
return await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/");
//AuthenticationContext? authContext = new AuthenticationContext(authority);
//ClientCredential clientCred = new ClientCredential(s_clientId, s_clientSecret);
//AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
//if (result == null)
//{
// throw new InvalidOperationException($"Failed to retrieve an access token for {resource}");
//}
//return result.AccessToken;
}
}
This code does not throw any exceptions, and it works for non-encrypted queries. But for encrypted queries I get the following error:
Failed to decrypt a column encryption key. Invalid key store provider name: 'AZURE_KEY_VAULT'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered) custom key store provider names are: . Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly. Failed to decrypt a column encryption key. Invalid key store provider name: 'AZURE_KEY_VAULT'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: 'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'. Valid (currently registered) custom key store provider names are: . Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.
It seems like the key vault provider is not registered.
What should I do to make it work to query encrypted data?
packages used
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.6.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="2.1.0" />
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
I was able to use this code that uses provides a TokenCredential to the SqlColumnEncryption provider. DefaultAzureCredential returns a managed identity when deployed as an App Service:
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider }
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Call it from your startup.Configure method.
Turns out, it is impossible to read decrypted data in .NET 5 when MSI is used. There is a bug in MS packages and the app service is never authorized.
You have to use Service Principal. This works like a charm!
Update
I have to thank to MS engineers that offered a working solution:
public static async Task<string> KeyVaultAuthenticationCallback(string authority, string resource, string scope)
{
return await Task.Run(() => new ManagedIdentityCredential().GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
/********************** Alternatively, to use User Assigned Managed Identity ****************/
// var clientId = {clientId_of_UserAssigned_Identity};
// return await Task.Run(() => new ManagedIdentityCredential(clientId).GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
}
It's not a problem with .NET 5. You've taken the example authentication callback for Azure Key Vault and changed it to be specific to Azure SQL DB instead of the AKV resource. You need to adjust your callback to get a valid AKV token. This is just one way to get a token using the Azure.Core and Azure.Identity libraries:
private static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope)
{
return await Task.Run(() => new ManagedIdentityCredential().GetToken(new TokenRequestContext(new string [] {"https://vault.azure.net/.default"})).Token);
}
Answer for Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider v3
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new(new ManagedIdentityCredential());
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new()
{
[SqlColumnEncryptionAzureKeyVaultProvider.ProviderName] = azureKeyVaultProvider
};
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
Taken from microsoft/sql-server-samples after almost losing my mind.
Apart from that, it's required to:
Assign app service managed identity Key Vault Crypto User role in Key Vault's IAM tab.
Grant app service managed identity DB user VIEW ANY COLUMN MASTER KEY DEFINITION and VIEW ANY COLUMN ENCRYPTION KEY DEFINITION permissions:
GRANT VIEW ANY COLUMN MASTER KEY DEFINITION to [app-service-user]
GRANT VIEW ANY COLUMN ENCRYPTION KEY DEFINITION to [app-service-user]
Allow app service access through Key Vault's firewall (i.e. adding app service outbound IPs to Key Vault's firewall rules).
Scenario:
I have an angular5 client application, which uses hello.js to authenticate users using their office 365 credentials.
Client Code:
hello.init({
msft: {
id: configuration.AppID,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/' + configuration.TenantID + '/oauth2/v2.0/authorize'
},
scope_delim: ' ',
form: false
},
},
{ redirect_uri: configuration.redirecturl }
);
}
login() {
hello('msft').login({ scope: 'User.Read People.Read', display: 'popup' })
.then((authData: any) => { // console.log(authData);
this.zone.run(() => {
// get profile
}
A successful response is (Manipulated for security reasons)
{
"msft":{
"access_token":"REMOVED TOKEN HERE",
"token_type":"Bearer",
"expires_in":3599,
"scope":"basic,User.Read,People.Read",
"state":"",
"session_state":"3b82898a-2b3f-445363f-89ae-d9696gg64ad3",
"client_id":"672330148-2bb43-3080-9eee-1f46311f789c",
"network":"msft",
"display":"popup",
"redirect_uri":"http://localhost:5653/",
"expires":15245366.218
}
}
The decoded access_token has these few keys:
Header:
1. nonce (requires some special processing, I couldn't find any documentation regarding special processing)
2. typ: JWT
PayLoad:
"aud": "https://graph.microsoft.com",
Once the access_token is received, I am sending the access_token in authorization header of every call to my backend API. The goal is to validate the token and only send a successful response if the access_token is validated and authorized. If unsuccessful, 401 Unauthorized is the response.
API Code to validate access_token, ASP .NET CORE 2, Following (https://auth0.com/blog/securing-asp-dot-net-core-2-applications-with-jwts/)
namespace JWT
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddMvc();
}
}
}
// other methods
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
In appsettings.json I have:
{ "Jwt": {
"Key": "verySecretKey", **(I got the key from https://login.microsoftonline.com/common/discovery/keys with the kid value in access_token header)**
"Issuer": "https://sts.windows.net/49bcf059-afa8-4bf9-8470-fad0c9cce27d/", } }
Finally, the error I receive is :
"WWW-Authenticate →Bearer error="invalid_token", error_description="The signature key was not found""
I have been stuck here since past few days, any help will be life savior.
Key Points:
I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.
The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?
Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.
Please let me know if you need more information.
I tried to validate the access_token in jwt.io (https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx) but I was not able to.
Microsoft Graph API access tokens are signed differently from other access tokens from what I can see.
You do not need to validate tokens that are meant for another API, it is their job.
The aud here is https://graph.microsoft.com, I am not sure if I need to and why do I need to change aud to my client id. how do I do that?
I don't know about HelloJS, but you should be able to get an Id token after authentication with response_type=id_token token.
Then you need to attach that to the requests.
It should have your client id as the audience.
Is there something wrong in the code or do i need to tweak the way I am requesting header tokens.
The only thing that stands out to me is that you are doing a lot of unnecessary configuration.
Basically the configuration should be:
.AddJwtBearer(o =>
{
o.Audience = "your-client-id";
o.Authority = "https://login.microsoftonline.com/your-tenant-id/v2.0";
})
The handler will automatically fetch the public signing keys on startup.
It's not really a good idea to hard-code signing keys in your app since your app will break when AAD finishes signing key rollover.
I also spent a lot of time trying to validate it, but the bottom line is that you can't:
Access tokens are opaque blobs of text that are for the resource only. If you're a client getting a token for Graph, assume that it's an encrypted string that you should never look at - sometimes it will be. We use a special token format for Graph that they know how to validate - you shouldn't be looking at access tokens if they're not for you. (source: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609)
Instead of using the access token, you should create an ID token, which is a regular JWT token that can be validated like any other JWT:
Get the public key from the Microsoft directory
Validate the signature, audience, issuer, etc.
To get an ID token using the MSAL API after login you can do (javascript example):
const { instance, accounts } = useMsal();
const request = {
scopes: ["User.Read"],
account: accounts[0]
};
const idToken = await instance.acquireTokenSilent(request).idToken;
For more information on ID Tokens, please check:
https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens
For more information on opaque tokens, please check:
https://zitadel.com/blog/jwt-vs-opaque-tokens
Yeah, this took a bit to work through. For anyone else researching this, here's my understanding.
You don't use the Microsoft Graph API to secure your web api. Instead:
The client continues to use the Microsoft Identity Platform to authenticate.
The client uses the resulting JWT access token to call the Web API as normal for OAuth 2.0 flow
The web API uses JwtBearerAuthenticationScheme, setting the authority to the Microsoft identity platform. See this example and search for JwtBearerAuthenticationScheme.
The web API uses the provided access token to obtain an 'On Behalf Of' user token.
The web API calls the Graph API using this 'On Behalf Of' token. This token has a different lifespan than the token the client obtained, and refreshes must be handled separately.
This is a very distilled version of this example. Disclaimer: I haven't put this into practice yet.
I have one Authorization server and one resource server. I am creating access token at authorization server and try to use it at Resource server using RemoteTokenServices in oauth2 which hits '/oauth/check_token' internally to authorization server, where it only checks for token existence and its expiry. But it does not check for roles/scopes against endpoint given vs roles/scopes against access_token.
#FrameworkEndpoint
public class CheckTokenEndpoint {
#RequestMapping(value = "/oauth/check_token")
#ResponseBody
public Map<String, ?> checkToken(#RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, ?> response = accessTokenConverter.convertAccessToken(token, authentication);
return response;
}
}
Above code snippet is from CheckTokenEndpoint.java.
Is there any way to achieve roles/scopes based authorization also?
If anyone else come across a similar issue with JWT token implementation using XML-based configuration, I have solved it the following way
Oh and my detailed post on how to implement Spring OAuth2 using XML-based configuration can be found here
Some assumptions
You are using JWT tokens that have custom claims
You have provided a custom implementation of JwtAccessTokenConvertor which in turn implements TokenEnhancer interface (feel free to implement AccessTokenConvertor & TokenEnhancer interfaces without having to use JwtAccessTokenConvertor)
You are using XML-based configuration
A closer look at the CheckTokenEndpoint source code reveals the follow
private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
And looking at the source code of DefaultAccessTokenConvertor, it is the default implementation of AccessTokenConvertor interface which basically have the following contracts
Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
OAuth2Authentication extractAuthentication(Map<String, ?> map);
In my case, I used JWT tokens, meaning that the token value that I pass to the /oauth/token_check endpoint is a signed (with RSA keypair) JWT and the TokenCheckEndpoint will do a few checks such as
Checks if the token is in the db (oauth_access_token table), this does not apply to JWT implementation as they are not necessarily stored in db
Check that its valid JWT token in the first place
Check that the signature of the token is correct and it has not be tampered
Check that its not expired
Other checks that I don't know of
In addition to the above, I needed to check that the custom claim such as scope (i.e. basically role and its associated permissions) is same in the database (making sure that roles didn't change since token was issued).
Based on my debugging, when the /oauth/check_token endpoint is hit, the extractAccessToken followed by extractAuthentication methods is called respectively (at least with JWT implementation).
Since I have extended JwtAccessTokenConvertor (which in turn implements AccessTokenConvertor & TokenEnhancer interfaces) in order to enhance my JWT token to add custom claims (i.e. scope) to it by overriding the enhance method as shown below
#Component
public class MyJwtAccessTokenConvertor extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
//enhance the token with custom claims (i.e. user role scope)
//then return it
return result;
}
#Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
OAuth2AccessToken mytoken = tokenConverter.extractAccessToken(value, map);
/* validate the custom claims of token i.e. user role scopes
* and if any issue throw an exception
*/
return token;
}
}
I could easily validate that the JWT access token has the required user role scopes in the extractAccessToken method. If I detect any violation then I throw InvalidTokenException (can be custom exception too).
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
When the user is authenticated I put a signed token in the response authorization header.
Every furthere access on a ressource url is only allowed with a valid signed token.
When I create the token and valdiate it:
var principal = tokenHandler.ValidateToken(tokenString, validationParameters);
then I get the principal (user who made the request) when the signed key is the same which got
used by creating the token.
That I can use the same signed key after authentication and during the ressource request to validate the token I have created this class:
public static class ApiConstants
{
private static readonly RNGCryptoServiceProvider CryptoProvider = new RNGCryptoServiceProvider(new byte[33]);
private static byte[] key = new byte[32];
static ApiConstants()
{
CryptoProvider.GetBytes(key);
}
public static byte[] GetSignedKey()
{
return key;
}
}
Is there anything wrong that I put this code in a static class which is actually my full purpose as I want the filling up of the byte array with random numbers to happen only one time!?
Is there still something I can improve?
You Can't make the signature token as static. because then it will become as global variable and shared by all request(thread). Also you will face concurrency issue with static field.
If you want to make it session specific. then you need to store in a session variable not in a static field.