Unable to access secret 1 whereas secret 2 is accessible which are part of same Azure Key vault - azure-keyvault

I have two secrets in one azure key vault, Secret-1 and Secret-2. Using Clinet ID, Client Secret, base URL I am able to access Secret-1, but whereas Secret-2 is not accessible, which is in the same azure key vault. It is throwing "Microsoft.Azure.KeyVault: Operation returned an invalid status code 'NotFound'" error. Can someone please suggest where we might be missing and are unable to access "Secret-2".
Code
main function code
main function()
{
kvc = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
SecretBundle secret = Task.Run(() => kvc.GetSecretAsync(baseSecretURI + #"secrets/" +
secretName)).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new System.InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken; // error thrown at this line when trying to access Secret-2
}

The NotFound error is usually an indicator that there is not a secret in the instance of Azure Key Vault that matches what you are requesting. Can you confirm that there is a secret with the name of what you are requesting in the instance of Azure Key Vault?
Workaround: Remove the secret from the key vault and generate a new one and try again.
I test with the following code which comes from the code you provided.
var kvc = new KeyVaultClient(async (authority, resource, scope) =>
{
var adCredential = new ClientCredential(clientId,clientSecret);
var authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority, null);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resource, adCredential);
return authenticationResult.AccessToken;
});
SecretBundle secret = Task.Run(() => kvc.GetSecretAsync(baseSecretURI + #"secrets/" + secretName)).ConfigureAwait(false).GetAwaiter().GetResult();

Related

Best practice when using OpenIddict application properties

I'm using the Properties column in my OpenIddict applications to store some metadata about an application instead of using a custom entity - comments on this post custom properties within token imply that's what its meant for.
However, despite reading the above post I'm struggling to understand how best to implement this.
Currently my "create application" looks like this:
public interface IOpenIddictAppService
{
Task<CreateOpenIddictAppResponseDto> CreateAsync(CreateOpenIddictAppRequestDto appDto);
}
public class OpenIddictAppService : IOpenIddictAppService
{
private readonly IOpenIddictApplicationManager _appManager;
public OpenIddictAppService(IOpenIddictApplicationManager appManager)
{
_appManager = appManager;
}
public async Task<CreateOpenIddictAppResponseDto> CreateAsync(CreateOpenIddictAppRequestDto appDto)
{
var sha = SHA512.Create();
var clientId = Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())));
var clientSecret = SecureRandomStringHelper.Create(StaticData.ClientSecretSize);
var data = new OpenIddictApplicationDescriptor
{
ClientId = clientId,
ClientSecret = clientSecret,
DisplayName = appDto.Name,
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
OpenIddictConstants.Permissions.Prefixes.Scope + "api",
OpenIddictConstants.Permissions.ResponseTypes.Code
}
};
// Postman test URL"https://oauth.pstmn.io/v1/callback"
appDto.RedirectUrls.Split(" ").ToList().ForEach(x => data.RedirectUris.Add(new Uri(x)));
data.Properties.Add("IdentityConfig", JsonSerializer.SerializeToElement(new AppIdentityProperties
{
ClientSystemId = appDto.ClientSystemId,
CustomerAccountId = appDto.CustomerAccountId
}));
var app = await _appManager.CreateAsync(data);
CreateOpenIddictAppResponseDto result = new()
{
Id = new Guid(await _appManager.GetIdAsync(app) ?? throw new ArgumentNullException("OpenIddictApplication not found")),
ClientId = clientId, // Send back the client id used
ClientSecret = clientSecret, // Send back the secret used so it can be displayed one-time-only for copy/paste
RedirectUrls = string.Join(" ", await _appManager.GetRedirectUrisAsync(app))
};
return result;
}
}
And I'm loading it like this in my authorization controller:
[HttpPost("~/connect/token")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
ClaimsPrincipal claimsPrincipal;
if (request.IsClientCredentialsGrantType())
{
// Note: the client credentials are automatically validated by OpenIddict:
// if client_id or client_secret are invalid, this action won't be invoked.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
if (application == null)
{
throw new InvalidOperationException("The application details cannot be found in the database.");
}
// Create the claims-based identity that will be used by OpenIddict to generate tokens.
var identity = new ClaimsIdentity(
authenticationType: TokenValidationParameters.DefaultAuthenticationType,
nameType: Claims.Name,
roleType: Claims.Role);
// Add the claims that will be persisted in the tokens (use the client_id as the subject identifier).
identity.AddClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application))
.AddClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application));
var properties = await _applicationManager.GetPropertiesAsync(application);
if (properties.Any(o => o.Key == "IdentityConfig"))
{
var identityConfig = JsonSerializer.Deserialize<AppIdentityProperties>(properties.FirstOrDefault(o => o.Key == "IdentityConfig").Value);
if (identityConfig != null)
{
identity.AddClaim(StaticData.Claims.ClientSystem, identityConfig.ClientSystemId.ToString())
.AddClaim(StaticData.Claims.CustomerAccount, identityConfig.CustomerAccountId.ToString());
}
}
identity.SetDestinations(static claim => claim.Type switch
{
// Allow the "name" claim to be stored in both the access and identity tokens
// when the "profile" scope was granted (by calling principal.SetScopes(...)).
Claims.Name when claim.Subject.HasScope(Scopes.Profile)
=> new[] { Destinations.AccessToken, Destinations.IdentityToken },
// Otherwise, only store the claim in the access tokens.
_ => new[] { Destinations.AccessToken }
});
// Note: In the original OAuth 2.0 specification, the client credentials grant
// doesn't return an identity token, which is an OpenID Connect concept.
//
// As a non-standardized extension, OpenIddict allows returning an id_token
// to convey information about the client application when the "openid" scope
// is granted (i.e specified when calling principal.SetScopes()). When the "openid"
// scope is not explicitly set, no identity token is returned to the client application.
// Set the list of scopes granted to the client application in access_token.
claimsPrincipal = new ClaimsPrincipal(identity);
claimsPrincipal.SetScopes(request.GetScopes());
claimsPrincipal.SetResources(await _scopeManager.ListResourcesAsync(claimsPrincipal.GetScopes()).ToListAsync());
}
else if (request.IsAuthorizationCodeGrantType())
{
// Retrieve the claims principal stored in the authorization code
claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
}
else if (request.IsRefreshTokenGrantType())
{
// Retrieve the claims principal stored in the refresh token.
claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
}
else
{
throw new InvalidOperationException("The specified grant type is not supported.");
}
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
Now - it's working...but I have a sneaky feeling that I'm just using it wrong especially based on Kevin Chalet's comments on that post... I think I'm probably also setting the redirect URLs the wrong way too!
Can anyone give me any more specific guidance on how I should really be doing this.

Google OAuth Returning Additional Scopes Without Requesting

I was testing around with Google's oauth and was trying out different scopes.
However, I then reduced my scope request to just this : "https://www.googleapis.com/auth/userinfo.email"
The following is more in dotnetcore
Dictionary<string, string> queries = new Dictionary<string, string>();
queries.Add("scope", "https://www.googleapis.com/auth/userinfo.email");
queries.Add("access_type", "offline");
queries.Add("include_granted_scopes" ,"true");
queries.Add("response_type", "code");
queries.Add("state", "state");
queries.Add("redirect_uri", "http://localhost:5000/api/authenticate/googauth");
queries.Add("client_id", _clientId);
queries.Add("prompt", "consent");
UriBuilder builder = new UriBuilder();
builder.Host = "accounts.google.com";
builder.Scheme = "https";
builder.Path = "o/oauth2/v2/auth";
//builder.Query = ""
foreach (var query in queries)
{
if (string.IsNullOrEmpty(builder.Query))
{
builder.Query += $"{query.Key}={query.Value}";
}
else
{
builder.Query += $"&{query.Key}={query.Value}";
}
}
var redirectUri = builder.Uri.ToString();
return Redirect(redirectUri);
From the returned code, I then retrieved the access token etc.
Dictionary<string, string> values = new Dictionary<string, string>();
values.Add("code", code);
values.Add("client_id", _clientId);
values.Add("client_secret",_clientSecret);
values.Add("redirect_uri", "http://localhost:5000/api/authenticate/googauth");
values.Add("grant_type", "authorization_code");
var client = new HttpClient();
var result = await client.PostAsync("https://oauth2.googleapis.com/token", new FormUrlEncodedContent(values));
var content = await result.Content.ReadAsStringAsync();
var convertedContent = JsonSerializer.Deserialize<GoogleAccesstoken>(content);
However, I seem to get more than what I asked for. I get this in the returned scopes :
openid https://www.googleapis.com/auth/user.gender.read https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/user.birthday.read
I've tried using incognito, and different browsers and they all return the same thing (thinking that it may have been a cache issue).
Is anyone able to help me on this?
Thanks.
Enables applications to use incremental authorization to request access to additional scopes in context. If you set this parameter's value to true and the authorization request is granted, then the new access token will also cover any scopes to which the user previously granted the application access. See the incremental authorization section for examples.
extract from google documentation: https://developers.google.com/identity/protocols/oauth2/web-server
Basically means that the user has previously granted you the other scopes. Could have been through a login screen or something where you have used the same clientId

Uploading a x509 cert to Application Manifest on Azure ADD or Microsoft Registration Portal

Sorry for the multiple post about the same issue!
I'm trying to upload a self signed sertificate to application manifest created on Microsoft Registration Portal but I have some issues which I don't completly understand why, According to this answer, it's very much possible to upload the certificate using DELEGATED PERMISSIONS however I don't see the reason why I can't use Application Permissions since I only need the AccessToken and I get that with the client_credential grant flow,
Below is the code that I have tried but when retrieving the token with client_credential grant flow, I get stuck att var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
and when trying to use the code given to my by Tom Sung in the previous post, the applications exits with error "must have client_credentil or client_assertion in request body"
this is the code that I have tried:
private static async Task<string> GetAppTokenAsync(string graphResourceId, string tenantId, string clientId, string userId)
{
string aadInstance = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authenticationContextt =
new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/oauth2/token");
AuthenticationResult result =
await authenticationContextt.AcquireTokenAsync(graphResourceId,
clientCredential);
//token is acquiered and gets stuck
var e = result.AccessToken;
//Tom Suns code
IPlatformParameters parameters = new PlatformParameters(PromptBehavior.SelectAccount);
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance);
var authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientId, new Uri("http://localhost"), parameters, new UserIdentifier(userId, UserIdentifierType.UniqueId));
//exits with error
return authenticationResult.AccessToken;
}
try
{
var graphResourceId = "https://graph.windows.net";
var userId = "****";
//used to test if token is acquired
//var tokennn = await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId);
var servicePointUri = new Uri(graphResourceId);
var serviceRoot = new Uri(servicePointUri, tenant);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync(graphResourceId, tenantID, ClientId, userId));
AsymmetricKeyParameter myCAprivateKey = null;
//generate a root CA cert and obtain the privateKey
X509Certificate2 MyRootCAcert = CreateCertificateAuthorityCertificate("CN=OutlookIntegration", out myCAprivateKey);
//add CA cert to store
addCertToStore(MyRootCAcert, StoreName.Root, StoreLocation.LocalMachine);
var expirationDate = DateTime.Parse(MyRootCAcert.GetExpirationDateString()).ToUniversalTime();
var startDate = DateTime.Parse(MyRootCAcert.GetEffectiveDateString()).ToUniversalTime();
var binCert = MyRootCAcert.GetRawCertData();
var keyCredential = new KeyCredential
{
CustomKeyIdentifier = MyRootCAcert.GetCertHash(),
EndDate = expirationDate,
KeyId = Guid.NewGuid(),
StartDate = startDate,
Type = "AsymmetricX509Cert",
Usage = "Verify",
Value = binCert
};
//gets stuck here when using clientsecret grant type
var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
application.KeyCredentials.Add(keyCredential);
application.UpdateAsync().Wait();
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
I am now completly stuck, Anyone have any idea why it doesn't work with Application Permissions or why it gets stuck at var application = activeDirectoryClient.Applications["ApplicationObjectId"].ExecuteAsync().Result;
Edit 1
is it because I have my app as a web app/API that uses username and password to authenticate?
Based on my test if we want to change the keyCredential, DELEGATED PERMISSIONS is required.
If we want to update Azure AD application other properties, we could use Application Permissions.
Reference:
Azure Active Directory developer glossary
"Delegated" permissions, which specify scope-based access using delegated authorization from the signed-in resource owner, are presented to the resource at run-time as "scp" claims in the client's access token.
"Application" permissions, which specify role-based access using the client application's credentials/identity, are presented to the resource at run-time as "roles" claims in the client's access token.

OpenIdConnectAuthenticationOptions and AcquireTokenByAuthorizationCodeAsync: Invalid JWT token

I'm trying to authorization code, and then hopefully a refresh token, with the OWIN OIDC middleware. However, I'm getting this error:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: 'AADSTS50027: Invalid JWT token. AADSTS50027: Invalid JWT token. Token format not valid.
Trace ID: 8622dfea-05cd-4080-a52c-ec95a9593800
Correlation ID: 1cf57566-1e02-4856-a4bc-357d5b16ae8a
Note that the authentication part works: I do get the original IdToken back, and the SecurityTokenValidated Notifications event fires. The error above occurs on the "AcquireTokenByAuthorizationCodeAsync" line.
What I'm trying to do is use IdentityServer as an IdP inbetween Azure AD (upstream) and my client (downstream), and I need to capture the refresh token to validate against AAD when then client tries to use the downstream refresh token, so that I don't issue access tokens when the AAD user has been locked out or removed.
var authority = "https://login.microsoftonline.com/xxx.onmicrosoft.com/v2.0";
var clientId = "xxx-30f5-47c2-9ddb-b5fcfd583f96";
var redirectUri = "http://localhost:60546/oidcCallback";
var clientSecret = "c8RRB4DCUiXMPEotQh2jm2ArgpYAqUMjGhDRKuuJOxxx";
var oidc = new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
Caption = "OIDC",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = redirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = false },
SignInAsAuthenticationType = signInAsType,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async e =>
{
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(e.ProtocolMessage.Code, new Uri(redirectUri), new ClientAssertion(clientId, clientSecret));
logger.Info(result.IdToken);
}
}
};
app.UseOpenIdConnectAuthentication(oidc);
Thanks!
One thing that I can see is wrong is that you should use ClientCredential, not ClientAssertion:
var result =
await authContext.AcquireTokenByAuthorizationCodeAsync(
e.ProtocolMessage.Code,
new Uri(redirectUri),
new ClientCredential(clientId, clientSecret));
And then the second thing. You are using ADAL, but seems like you are using the v2 endpoint. I assume you registered the app at apps.dev.microsoft.com?
In that case you should use MSAL (https://www.nuget.org/packages/Microsoft.Identity.Client).
The API for MSAL is a bit different, you use a class called ConfidentialClientApplication instead of AuthenticationContext (in this case). Here is a snippet from a sample app:
var cca = new ConfidentialClientApplication(clientId, redirectUri, new ClientCredential(appKey), userTokenCache, null);
string[] scopes = { "Mail.Read" };
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, scopes);
Sample app: https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-v2

Store authentification data in MVC

I have created a custom Authorize attribute where I use the Office Graph to get AAD groups the current user is member of, and based on those I reject or authorize the user. I want to save the groups, because the call to Office Graph takes some performance. What would be the correct way to save that kind of data? I can see some people saves it to a SQL server, but then I would need to ensure cleanup etc.
Also I can see in some threads the session state is stated to be a bad choice due to concurrency. So the question is what options do you have to store this kind of information?
All suggestions are welcome.
If you were only using the group_id info, there is no need to use Office Graph and store it at all. We can enable Azure AD issue the groups claims by change the manifest of Azure AD like below:(refer this code sample)
"groupMembershipClaims": "All",
And if you are also using other info about groups, you can store these info into claims. Here is a code sample that add the name of groups into claims for your reference:
AuthorizationCodeReceived = async context =>
{
ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(Globals.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigHelper.Authority, new TokenDbCache(userObjectId));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);
ActiveDirectoryClient graphClient = new ActiveDirectoryClient(new Uri(ConfigHelper.GraphServiceRoot),
async () => { return await Task.FromResult(result.AccessToken); }
);
try
{
foreach (var groupClaim in context.AuthenticationTicket.Identity.FindAll("groups"))
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri($"https://graph.windows.net/adfei.onmicrosoft.com/groups/{groupClaim.Value}?api-version=1.6"),
Method = HttpMethod.Get,
};
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage httpResponse = httpClient.SendAsync(request).Result;
var retJSON = httpResponse.Content.ReadAsStringAsync().Result;
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(retJSON);
((ClaimsIdentity)context.AuthenticationTicket.Identity).AddClaim(new Claim("groupName", dict["displayName"].ToString()));
}
}
}
catch (Exception ex)
{
}
},
Then we can these info from controller using the code below:
ClaimsPrincipal.Current.FindAll("groupName")

Resources