Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AzureCliCredential utilizes expires_on for token expiration #41366

Merged
merged 3 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Claims from the `TokenRequestContext` are now correctly sent through to MSAL in `ConfidentialClient` credentials. [#40451](https://github.com/Azure/azure-sdk-for-net/issues/40451).

### Other Changes
- `AzureCliCredential` utilizes the new `expires_on` property returned by `az account get-access-token` to determine token expiration.

## 1.10.4 (2023-11-13)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ private static AccessToken DeserializeOutput(string output)

JsonElement root = document.RootElement;
string accessToken = root.GetProperty("accessToken").GetString();
DateTimeOffset expiresOn = root.TryGetProperty("expiresIn", out JsonElement expiresIn)
? DateTimeOffset.UtcNow + TimeSpan.FromSeconds(expiresIn.GetInt64())
DateTimeOffset expiresOn = root.TryGetProperty("expires_on", out JsonElement expires_on)
? DateTimeOffset.FromUnixTimeSeconds(expires_on.GetInt64())
: DateTimeOffset.ParseExact(root.GetProperty("expiresOn").GetString(), "yyyy-MM-dd HH:mm:ss.ffffff", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal);

return new AccessToken(accessToken, expiresOn);
Expand Down
24 changes: 18 additions & 6 deletions sdk/identity/Azure.Identity/tests/AzureCliCredentialTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,28 @@ public async Task AuthenticateWithCliCredential(
}

[Test]
public async Task AuthenticateWithCliCredential_ExpiresIn()
public async Task AuthenticateWithCliCredential_expires_on()
{
var (expectedToken, expectedExpiresOn, processOutput) = CredentialTestHelpers.CreateTokenForAzureCliExpiresIn(1800);
var now = DateTimeOffset.UtcNow;
DateTimeOffset expectedExpiresOn = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, TimeSpan.Zero).AddHours(1);
var (expectedToken1, processOutput1) = CredentialTestHelpers.CreateTokenForAzureCliExpiresOn(expectedExpiresOn, true);
var (expectedToken2, processOutput2) = CredentialTestHelpers.CreateTokenForAzureCliExpiresOn(expectedExpiresOn, false);

var testProcess = new TestProcess { Output = processOutput };
var testProcess = new TestProcess { Output = processOutput1 };
AzureCliCredential credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess)));
AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default));
AccessToken actualToken1 = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default));

Assert.AreEqual(expectedToken, actualToken.Token);
Assert.LessOrEqual(expectedExpiresOn, actualToken.ExpiresOn);
Assert.AreEqual(expectedToken1, actualToken1.Token, "The tokens should match.");
Assert.AreEqual(expectedExpiresOn, actualToken1.ExpiresOn, "The expires on value should be the same for token1.");

testProcess = new TestProcess { Output = processOutput2 };
credential = InstrumentClient(new AzureCliCredential(CredentialPipeline.GetInstance(null), new TestProcessService(testProcess)));
AccessToken actualToken2 = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default));

Assert.AreEqual(expectedToken2, actualToken2.Token);
Assert.AreEqual(expectedExpiresOn, actualToken2.ExpiresOn, "The expires on value should be the same for token2.");

Assert.AreEqual(actualToken1.ExpiresOn, actualToken2.ExpiresOn);
}

[Test]
Expand Down
13 changes: 8 additions & 5 deletions sdk/identity/Azure.Identity/tests/CredentialTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
using Azure.Identity.Tests.Mock;
using Microsoft.Identity.Client;
using NUnit.Framework;
using Castle.DynamicProxy;

namespace Azure.Identity.Tests
{
Expand All @@ -39,12 +38,16 @@ public static (string Token, DateTimeOffset ExpiresOn, string Json) CreateTokenF
return (token, expiresOn, json);
}

public static (string Token, DateTimeOffset ExpiresOn, string Json) CreateTokenForAzureCliExpiresIn(int seconds = 30)
public static (string Token, string Json) CreateTokenForAzureCliExpiresOn(DateTimeOffset expiresOn, bool includeExpiresOn)
{
var expiresOn = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(seconds);
const string expiresOnStringFormat = "yyyy-MM-dd HH:mm:ss.ffffff";

var expiresOnString = expiresOn.ToLocalTime().ToString(expiresOnStringFormat);
var token = TokenGenerator.GenerateToken(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), expiresOn.UtcDateTime);
var json = $"{{ \"accessToken\": \"{token}\", \"expiresIn\": {seconds} }}";
return (token, expiresOn, json);
var json = includeExpiresOn ?
$$"""{ "accessToken": "{{token}}", "expiresOn": "{{expiresOnString}}", "expires_on": {{expiresOn.ToUnixTimeSeconds()}} }""" :
$$"""{ "accessToken": "{{token}}", "expiresOn": "{{expiresOnString}}" }""";
return (token, json);
}

public static (string Token, DateTimeOffset ExpiresOn, string Json) CreateTokenForAzureDeveloperCli() => CreateTokenForAzureDeveloperCli(TimeSpan.FromSeconds(30));
Expand Down