Skip to content

Commit f62aa5b

Browse files
authored
Add auth token refresh to csharp (#20234)
* quick edit to allow tokens to refresh if they specify an expires_in and created fields. Also supports refresh_token if that is passed. * update samples * fix compile error for projects without default using statement. make the created date nullable. * fix nullable de-reference on Created. * fix to remove nullable reference types (the ! to remove the warning) and add documentation comments to the public members.
1 parent 3a37ba3 commit f62aa5b

File tree

16 files changed

+456
-136
lines changed

16 files changed

+456
-136
lines changed

modules/openapi-generator/src/main/resources/csharp/auth/OAuthAuthenticator.mustache

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,25 @@ namespace {{packageName}}.Client.Auth
1111
/// <summary>
1212
/// An authenticator for OAuth2 authentication flows
1313
/// </summary>
14-
public class OAuthAuthenticator : AuthenticatorBase
14+
public class OAuthAuthenticator : IAuthenticator
1515
{
16+
private TokenResponse{{nrt?}} _token;
17+
18+
/// <summary>
19+
/// Returns the current authentication token. Can return null if there is no authentication token, or it has expired.
20+
/// </summary>
21+
public string{{nrt?}} Token
22+
{
23+
get
24+
{
25+
if (_token == null) return null;
26+
if (_token.ExpiresIn == null) return _token.AccessToken;
27+
if (_token.ExpiresAt < DateTime.Now) return null;
28+
29+
return _token.AccessToken;
30+
}
31+
}
32+
1633
readonly string _tokenUrl;
1734
readonly string _clientId;
1835
readonly string _clientSecret;
@@ -31,7 +48,7 @@ namespace {{packageName}}.Client.Auth
3148
string{{nrt?}} scope,
3249
OAuthFlow? flow,
3350
JsonSerializerSettings serializerSettings,
34-
IReadableConfiguration configuration) : base("")
51+
IReadableConfiguration configuration)
3552
{
3653
_tokenUrl = tokenUrl;
3754
_clientId = clientId;
@@ -62,9 +79,8 @@ namespace {{packageName}}.Client.Auth
6279
/// <summary>
6380
/// Creates an authentication parameter from an access token.
6481
/// </summary>
65-
/// <param name="accessToken">Access token to create a parameter from.</param>
6682
/// <returns>An authentication parameter.</returns>
67-
protected override async ValueTask<Parameter> GetAuthenticationParameter(string accessToken)
83+
protected async ValueTask<Parameter> GetAuthenticationParameter()
6884
{
6985
var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token;
7086
return new HeaderParameter(KnownHeaders.Authorization, token);
@@ -76,31 +92,45 @@ namespace {{packageName}}.Client.Auth
7692
/// <returns>An authentication token.</returns>
7793
async Task<string> GetToken()
7894
{
79-
var client = new RestClient(_tokenUrl,
80-
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
81-
82-
var request = new RestRequest()
83-
.AddParameter("grant_type", _grantType)
84-
.AddParameter("client_id", _clientId)
85-
.AddParameter("client_secret", _clientSecret);
95+
var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
8696
97+
var request = new RestRequest();
98+
if (!string.IsNullOrWhiteSpace(_token?.RefreshToken))
99+
{
100+
request.AddParameter("grant_type", "refresh_token")
101+
.AddParameter("refresh_token", _token.RefreshToken);
102+
}
103+
else
104+
{
105+
request
106+
.AddParameter("grant_type", _grantType)
107+
.AddParameter("client_id", _clientId)
108+
.AddParameter("client_secret", _clientSecret);
109+
}
87110
if (!string.IsNullOrEmpty(_scope))
88111
{
89112
request.AddParameter("scope", _scope);
90113
}
91-
92-
var response = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
93-
114+
_token = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
94115
// RFC6749 - token_type is case insensitive.
95116
// RFC6750 - In Authorization header Bearer should be capitalized.
96117
// Fix the capitalization irrespective of token_type casing.
97-
switch (response.TokenType?.ToLower())
118+
switch (_token?.TokenType?.ToLower())
98119
{
99120
case "bearer":
100-
return $"Bearer {response.AccessToken}";
121+
return $"Bearer {_token.AccessToken}";
101122
default:
102-
return $"{response.TokenType} {response.AccessToken}";
123+
return $"{_token?.TokenType} {_token?.AccessToken}";
103124
}
104125
}
126+
127+
/// <summary>
128+
/// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request
129+
/// </summary>
130+
/// <param name="client"></param>
131+
/// <param name="request"></param>
132+
/// <returns></returns>
133+
public async ValueTask Authenticate(IRestClient client, RestRequest request)
134+
=> request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false));
105135
}
106136
}

modules/openapi-generator/src/main/resources/csharp/auth/TokenResponse.mustache

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{{>partial_header}}
22

3+
using System;
34
using Newtonsoft.Json;
45

56
namespace {{packageName}}.Client.Auth
@@ -10,5 +11,14 @@ namespace {{packageName}}.Client.Auth
1011
public string TokenType { get; set; }
1112
[JsonProperty("access_token")]
1213
public string AccessToken { get; set; }
14+
[JsonProperty("expires_in")]
15+
public int? ExpiresIn { get; set; }
16+
[JsonProperty("created")]
17+
public DateTime? Created { get; set; }
18+
19+
[JsonProperty("refresh_token")]
20+
public string{{nrt?}} RefreshToken { get; set; }
21+
22+
public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value);
1323
}
1424
}

samples/client/petstore/csharp/restsharp/net4.7/MultipleFrameworks/src/Org.OpenAPITools/Client/Auth/OAuthAuthenticator.cs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,25 @@ namespace Org.OpenAPITools.Client.Auth
1919
/// <summary>
2020
/// An authenticator for OAuth2 authentication flows
2121
/// </summary>
22-
public class OAuthAuthenticator : AuthenticatorBase
22+
public class OAuthAuthenticator : IAuthenticator
2323
{
24+
private TokenResponse _token;
25+
26+
/// <summary>
27+
/// Returns the current authentication token. Can return null if there is no authentication token, or it has expired.
28+
/// </summary>
29+
public string Token
30+
{
31+
get
32+
{
33+
if (_token == null) return null;
34+
if (_token.ExpiresIn == null) return _token.AccessToken;
35+
if (_token.ExpiresAt < DateTime.Now) return null;
36+
37+
return _token.AccessToken;
38+
}
39+
}
40+
2441
readonly string _tokenUrl;
2542
readonly string _clientId;
2643
readonly string _clientSecret;
@@ -39,7 +56,7 @@ public OAuthAuthenticator(
3956
string scope,
4057
OAuthFlow? flow,
4158
JsonSerializerSettings serializerSettings,
42-
IReadableConfiguration configuration) : base("")
59+
IReadableConfiguration configuration)
4360
{
4461
_tokenUrl = tokenUrl;
4562
_clientId = clientId;
@@ -70,9 +87,8 @@ public OAuthAuthenticator(
7087
/// <summary>
7188
/// Creates an authentication parameter from an access token.
7289
/// </summary>
73-
/// <param name="accessToken">Access token to create a parameter from.</param>
7490
/// <returns>An authentication parameter.</returns>
75-
protected override async ValueTask<Parameter> GetAuthenticationParameter(string accessToken)
91+
protected async ValueTask<Parameter> GetAuthenticationParameter()
7692
{
7793
var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token;
7894
return new HeaderParameter(KnownHeaders.Authorization, token);
@@ -84,31 +100,45 @@ protected override async ValueTask<Parameter> GetAuthenticationParameter(string
84100
/// <returns>An authentication token.</returns>
85101
async Task<string> GetToken()
86102
{
87-
var client = new RestClient(_tokenUrl,
88-
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
89-
90-
var request = new RestRequest()
91-
.AddParameter("grant_type", _grantType)
92-
.AddParameter("client_id", _clientId)
93-
.AddParameter("client_secret", _clientSecret);
103+
var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
94104

105+
var request = new RestRequest();
106+
if (!string.IsNullOrWhiteSpace(_token?.RefreshToken))
107+
{
108+
request.AddParameter("grant_type", "refresh_token")
109+
.AddParameter("refresh_token", _token.RefreshToken);
110+
}
111+
else
112+
{
113+
request
114+
.AddParameter("grant_type", _grantType)
115+
.AddParameter("client_id", _clientId)
116+
.AddParameter("client_secret", _clientSecret);
117+
}
95118
if (!string.IsNullOrEmpty(_scope))
96119
{
97120
request.AddParameter("scope", _scope);
98121
}
99-
100-
var response = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
101-
122+
_token = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
102123
// RFC6749 - token_type is case insensitive.
103124
// RFC6750 - In Authorization header Bearer should be capitalized.
104125
// Fix the capitalization irrespective of token_type casing.
105-
switch (response.TokenType?.ToLower())
126+
switch (_token?.TokenType?.ToLower())
106127
{
107128
case "bearer":
108-
return $"Bearer {response.AccessToken}";
129+
return $"Bearer {_token.AccessToken}";
109130
default:
110-
return $"{response.TokenType} {response.AccessToken}";
131+
return $"{_token?.TokenType} {_token?.AccessToken}";
111132
}
112133
}
134+
135+
/// <summary>
136+
/// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request
137+
/// </summary>
138+
/// <param name="client"></param>
139+
/// <param name="request"></param>
140+
/// <returns></returns>
141+
public async ValueTask Authenticate(IRestClient client, RestRequest request)
142+
=> request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false));
113143
}
114144
}

samples/client/petstore/csharp/restsharp/net4.7/MultipleFrameworks/src/Org.OpenAPITools/Client/Auth/TokenResponse.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010

11+
using System;
1112
using Newtonsoft.Json;
1213

1314
namespace Org.OpenAPITools.Client.Auth
@@ -18,5 +19,14 @@ class TokenResponse
1819
public string TokenType { get; set; }
1920
[JsonProperty("access_token")]
2021
public string AccessToken { get; set; }
22+
[JsonProperty("expires_in")]
23+
public int? ExpiresIn { get; set; }
24+
[JsonProperty("created")]
25+
public DateTime? Created { get; set; }
26+
27+
[JsonProperty("refresh_token")]
28+
public string RefreshToken { get; set; }
29+
30+
public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value);
2131
}
2232
}

samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/Auth/OAuthAuthenticator.cs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,25 @@ namespace Org.OpenAPITools.Client.Auth
1919
/// <summary>
2020
/// An authenticator for OAuth2 authentication flows
2121
/// </summary>
22-
public class OAuthAuthenticator : AuthenticatorBase
22+
public class OAuthAuthenticator : IAuthenticator
2323
{
24+
private TokenResponse _token;
25+
26+
/// <summary>
27+
/// Returns the current authentication token. Can return null if there is no authentication token, or it has expired.
28+
/// </summary>
29+
public string Token
30+
{
31+
get
32+
{
33+
if (_token == null) return null;
34+
if (_token.ExpiresIn == null) return _token.AccessToken;
35+
if (_token.ExpiresAt < DateTime.Now) return null;
36+
37+
return _token.AccessToken;
38+
}
39+
}
40+
2441
readonly string _tokenUrl;
2542
readonly string _clientId;
2643
readonly string _clientSecret;
@@ -39,7 +56,7 @@ public OAuthAuthenticator(
3956
string scope,
4057
OAuthFlow? flow,
4158
JsonSerializerSettings serializerSettings,
42-
IReadableConfiguration configuration) : base("")
59+
IReadableConfiguration configuration)
4360
{
4461
_tokenUrl = tokenUrl;
4562
_clientId = clientId;
@@ -70,9 +87,8 @@ public OAuthAuthenticator(
7087
/// <summary>
7188
/// Creates an authentication parameter from an access token.
7289
/// </summary>
73-
/// <param name="accessToken">Access token to create a parameter from.</param>
7490
/// <returns>An authentication parameter.</returns>
75-
protected override async ValueTask<Parameter> GetAuthenticationParameter(string accessToken)
91+
protected async ValueTask<Parameter> GetAuthenticationParameter()
7692
{
7793
var token = string.IsNullOrEmpty(Token) ? await GetToken().ConfigureAwait(false) : Token;
7894
return new HeaderParameter(KnownHeaders.Authorization, token);
@@ -84,31 +100,45 @@ protected override async ValueTask<Parameter> GetAuthenticationParameter(string
84100
/// <returns>An authentication token.</returns>
85101
async Task<string> GetToken()
86102
{
87-
var client = new RestClient(_tokenUrl,
88-
configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
89-
90-
var request = new RestRequest()
91-
.AddParameter("grant_type", _grantType)
92-
.AddParameter("client_id", _clientId)
93-
.AddParameter("client_secret", _clientSecret);
103+
var client = new RestClient(_tokenUrl, configureSerialization: serializerConfig => serializerConfig.UseSerializer(() => new CustomJsonCodec(_serializerSettings, _configuration)));
94104

105+
var request = new RestRequest();
106+
if (!string.IsNullOrWhiteSpace(_token?.RefreshToken))
107+
{
108+
request.AddParameter("grant_type", "refresh_token")
109+
.AddParameter("refresh_token", _token.RefreshToken);
110+
}
111+
else
112+
{
113+
request
114+
.AddParameter("grant_type", _grantType)
115+
.AddParameter("client_id", _clientId)
116+
.AddParameter("client_secret", _clientSecret);
117+
}
95118
if (!string.IsNullOrEmpty(_scope))
96119
{
97120
request.AddParameter("scope", _scope);
98121
}
99-
100-
var response = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
101-
122+
_token = await client.PostAsync<TokenResponse>(request).ConfigureAwait(false);
102123
// RFC6749 - token_type is case insensitive.
103124
// RFC6750 - In Authorization header Bearer should be capitalized.
104125
// Fix the capitalization irrespective of token_type casing.
105-
switch (response.TokenType?.ToLower())
126+
switch (_token?.TokenType?.ToLower())
106127
{
107128
case "bearer":
108-
return $"Bearer {response.AccessToken}";
129+
return $"Bearer {_token.AccessToken}";
109130
default:
110-
return $"{response.TokenType} {response.AccessToken}";
131+
return $"{_token?.TokenType} {_token?.AccessToken}";
111132
}
112133
}
134+
135+
/// <summary>
136+
/// Retrieves the authentication token (creating a new one if necessary) and adds it to the current request
137+
/// </summary>
138+
/// <param name="client"></param>
139+
/// <param name="request"></param>
140+
/// <returns></returns>
141+
public async ValueTask Authenticate(IRestClient client, RestRequest request)
142+
=> request.AddOrUpdateParameter(await GetAuthenticationParameter().ConfigureAwait(false));
113143
}
114144
}

samples/client/petstore/csharp/restsharp/net4.7/Petstore/src/Org.OpenAPITools/Client/Auth/TokenResponse.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010

11+
using System;
1112
using Newtonsoft.Json;
1213

1314
namespace Org.OpenAPITools.Client.Auth
@@ -18,5 +19,14 @@ class TokenResponse
1819
public string TokenType { get; set; }
1920
[JsonProperty("access_token")]
2021
public string AccessToken { get; set; }
22+
[JsonProperty("expires_in")]
23+
public int? ExpiresIn { get; set; }
24+
[JsonProperty("created")]
25+
public DateTime? Created { get; set; }
26+
27+
[JsonProperty("refresh_token")]
28+
public string RefreshToken { get; set; }
29+
30+
public DateTime? ExpiresAt => ExpiresIn == null ? null : Created?.AddSeconds(ExpiresIn.Value);
2131
}
2232
}

0 commit comments

Comments
 (0)