Skip to content

Commit ad03547

Browse files
authored
Merge pull request #28 from wparad/add-verify-token
Add VerifyToken method to the AuthressClient
2 parents b8d8e48 + a20718e commit ad03547

File tree

11 files changed

+466
-26
lines changed

11 files changed

+466
-26
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ This is the changelog for [Authress SDK](readme.md).
44
## 2.0 ##
55
* Renamed `AccessRecordStatements` and other models that end with `S` but aren't actually plural to be `AccessRecordStatement` (without the `S`).
66
* All APIs are now part of sub instance properties of the `AuthressClient` class, `AccessClient.AccessRecords` and `AccessClient.ServiceClients`, etc..
7+
* `ApiBasePath` has been renamed to `AuthressApiUrl`.
8+
* `HttpClientSettings` Has been removed in favor of `AuthressSettings` Class.
79

810
## 1.5 ##
911
* Fix `DateTimeOffset` type assignments, properties that were incorrectly defined as `DateTime` are now correctly `DateTimeOffsets`.
12+
* Add in `VerifyToken()` method to `AuthressClient`..
1013

1114
## 1.4 ##
1215
* Support exponential back-off retries on unexpected failures.

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ Installation:
2424

2525
* run: `dotnet add Authress.SDK` (or install via visual tools)
2626

27+
#### Verify Authress JWT
28+
The recommended solution is to use the C# built in OpenID provider by Microsoft. An example implementation is available in the [Authress C# Starter Kit](https://github.com/Authress/csharp-starter-kit/blob/main/src/Program.cs#L35). However, in some cases you might need to parse the JWT directly and verify it for use in serverless functions.
29+
30+
```csharp
31+
using Authress.SDK;
32+
33+
// Get an authress custom domain: https://authress.io/app/#/settings?focus=domain
34+
var authressSettings = new AuthressSettings { ApiBasePath = "https://authress.company.com", };
35+
var authressClient = new AuthressClient(tokenProvider, authressSettings)
36+
37+
var verifiedUserIdentity = await authressClient.VerifyToken(jwtToken);
38+
Console.WriteLine($"User ID: {verifiedUserIdentity.UserId}");
39+
```
40+
2741
#### Authorize users using user identity token
2842
```csharp
2943
using Authress.SDK;
@@ -46,7 +60,7 @@ namespace Microservice
4660
return accessToken;
4761
});
4862
// Get an authress custom domain: https://authress.io/app/#/settings?focus=domain
49-
var authressSettings = new AuthressSettings { ApiBasePath = "https://CUSTOM_DOMAIN.application.com", };
63+
var authressSettings = new AuthressSettings { ApiBasePath = "https://authress.company.com", };
5064
var authressClient = new AuthressClient(tokenProvider, authressSettings);
5165

5266
// 2. At runtime attempt to Authorize the user for the resource
@@ -103,7 +117,7 @@ namespace Microservice
103117
var decodedAccessKey = decrypt(accessKey);
104118
var tokenProvider = new AuthressClientTokenProvider(decodedAccessKey);
105119
// Get an authress custom domain: https://authress.io/app/#/settings?focus=domain
106-
var authressSettings = new AuthressSettings { ApiBasePath = "https://CUSTOM_DOMAIN.application.com", };
120+
var authressSettings = new AuthressSettings { ApiBasePath = "https://authress.company.com", };
107121
var authressClient = new AuthressClient(tokenProvider, authressSettings);
108122

109123
// Attempt to Authorize the user for the resource

contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ Update the API Key: https://www.nuget.org/account/apikeys using the Rhosys Devel
6060
* [ ] Remove LocalHost from the docs
6161
* [ ] Tests
6262
* [x] If-unmodified-since should called `expectedLastModifiedTime`, accept string or dateTime and convert this to an ISO String
63+
* [ ] Update OAuth2 openapi authentication references in the documentation

src/Authress.SDK/Client/AuthressClient.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Authress.SDK
1717
public partial class AuthressClient
1818
{
1919
private readonly HttpClientProvider authressHttpClientProvider;
20+
private readonly TokenVerifier tokenVerifier;
2021

2122
/// <summary>
2223
/// Get the permissions a user has to a resource. Get a summary of the permissions a user has to a particular resource.
@@ -27,6 +28,7 @@ public AuthressClient(ITokenProvider tokenProvider, AuthressSettings settings, I
2728
throw new ArgumentNullException("Missing required parameter AuthressSettings");
2829
}
2930
authressHttpClientProvider = new HttpClientProvider(settings, tokenProvider, customHttpClientHandlerFactory);
31+
tokenVerifier = new TokenVerifier(settings.ApiBasePath, authressHttpClientProvider);
3032
}
3133

3234
/// <summary>
@@ -38,17 +40,21 @@ public AuthressClient(ITokenProvider tokenProvider, HttpClientSettings settings,
3840
throw new ArgumentNullException("Missing required parameter HttpClientSettings");
3941
}
4042
authressHttpClientProvider = new HttpClientProvider(
41-
new AuthressSettings { ApiBasePath = settings?.ApiBasePath, RequestTimeout = settings?.RequestTimeout },
43+
new AuthressSettings { ApiBasePath = settings.ApiBasePath, RequestTimeout = settings.RequestTimeout },
4244
tokenProvider, customHttpClientHandlerFactory);
45+
tokenVerifier = new TokenVerifier(settings.ApiBasePath, authressHttpClientProvider);
46+
}
47+
48+
/// <summary>
49+
/// Verify a JWT token from anywhere. If it is valid a VerifiedUserIdentity will be returned. If it is invalid an exception will be thrown.
50+
/// </summary>
51+
/// <param name="jwtToken"></param>
52+
/// <returns>A verified user identity that contains the user's ID</returns>
53+
/// <exception cref="TokenVerificationException">Token is invalid in some way</exception>
54+
/// <exception cref="ArgumentNullException">One of the required parameters for this function was not specified.</exception>
55+
public async Task<VerifiedUserIdentity> VerifyToken(string jwtToken) {
56+
return await tokenVerifier.VerifyToken(jwtToken);
4357
}
44-
}
4558

46-
internal class AccessKey
47-
{
48-
public String Audience { get; set; }
49-
public String ClientId { get; set; }
50-
public String KeyId { get; set; }
51-
public String PrivateKey { get; set; }
52-
public String Algorithm { get; set; } = "RS256";
5359
}
5460
}

src/Authress.SDK/Client/AuthressClientTokenProvider.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616

1717
namespace Authress.SDK
1818
{
19+
internal class AccessKey
20+
{
21+
public String Audience { get; set; }
22+
public String ClientId { get; set; }
23+
public String KeyId { get; set; }
24+
public String PrivateKey { get; set; }
25+
public String Algorithm { get; set; } = "RS256";
26+
}
27+
1928
/// <summary>
2029
/// Provides the token from locally stored access key. Access key can be retrieved when creating a new client in the Authress UI.
2130
/// </summary>
@@ -47,8 +56,10 @@ public AuthressClientTokenProvider(string accessKeyBase64, string authressCustom
4756
this.accessKey = new AccessKey
4857
{
4958
Algorithm = "EdDSA",
50-
ClientId = accessKeyBase64.Split('.')[0], KeyId = accessKeyBase64.Split('.')[1],
51-
Audience = $"{accountId}.accounts.authress.io", PrivateKey = accessKeyBase64.Split('.')[3]
59+
ClientId = accessKeyBase64.Split('.')[0],
60+
KeyId = accessKeyBase64.Split('.')[1],
61+
Audience = $"{accountId}.accounts.authress.io",
62+
PrivateKey = accessKeyBase64.Split('.')[3]
5263
};
5364

5465
this.resolvedAuthressCustomDomain = (authressCustomDomain ?? $"{accountId}.api.authress.io").Replace("https://", "");
@@ -133,9 +144,9 @@ public Task<string> GetBearerToken(string authressCustomDomainFallback = null)
133144
{
134145
{ "iss", GetIssuer(authressCustomDomainFallback) },
135146
{ "sub", accessKey.ClientId },
136-
{ "exp", expiryDate.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
137-
{ "iat", now.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
138-
{ "nbf", now.Subtract(TimeSpan.FromMinutes(10)).Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
147+
{ "exp", (int)Math.Floor(expiryDate.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
148+
{ "iat", (int)Math.Floor(now.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
149+
{ "nbf", (int)Math.Floor(now.Subtract(TimeSpan.FromMinutes(10)).Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
139150
{ "aud", accessKey.Audience },
140151
{ "scopes", "openid" }
141152
};

src/Authress.SDK/Client/HttpClientProvider.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ public interface IHttpClientHandlerFactory
1717
}
1818

1919
/// <summary>
20-
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
20+
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
2121
/// </summary>
2222
public class HttpClientSettings
2323
{
2424
/// <summary>
25-
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
25+
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
2626
/// </summary>
2727
public string ApiBasePath { get; set; } = "https://api.authress.io";
2828

@@ -33,12 +33,12 @@ public class HttpClientSettings
3333
}
3434

3535
/// <summary>
36-
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
36+
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
3737
/// </summary>
3838
public class AuthressSettings
3939
{
4040
/// <summary>
41-
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
41+
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
4242
/// </summary>
4343
public string ApiBasePath { get; set; } = "https://api.authress.io";
4444

@@ -68,7 +68,7 @@ internal class HttpClientProvider
6868

6969
public HttpClientProvider(AuthressSettings settings, ITokenProvider tokenProvider, IHttpClientHandlerFactory customHttpClientHandlerFactory = null)
7070
{
71-
this.settings = settings;
71+
this.settings = settings ?? new AuthressSettings();
7272
this.tokenProvider = tokenProvider;
7373
this.customHttpClientHandlerFactory = customHttpClientHandlerFactory;
7474
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Converters;
5+
6+
namespace Authress.SDK.Client.JWT
7+
{
8+
[DataContract]
9+
internal class JwtPayload
10+
{
11+
12+
[DataMember(Name = "sub")]
13+
internal string Subject { get; set; }
14+
15+
[DataMember(Name = "iss")]
16+
internal string Issuer { get; set; }
17+
18+
[DataMember(Name = "exp")]
19+
[JsonConverter(typeof(UnixDateTimeConverter))]
20+
internal DateTimeOffset Expires { get; set; }
21+
}
22+
23+
[DataContract]
24+
internal class JwtHeader
25+
{
26+
27+
[DataMember(Name = "kid")]
28+
internal string KeyId { get; set; }
29+
}
30+
}

src/Authress.SDK/Client/OptimisticPerformanceHandler.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
using System.Text.RegularExpressions;
66
using System.Threading;
77
using System.Threading.Tasks;
8+
using Authress.SDK.Client.JWT;
89
using Microsoft.Extensions.Caching.Memory;
910
using Microsoft.IdentityModel.Tokens;
1011
using Newtonsoft.Json;
1112

1213
namespace Authress.SDK.Client
1314
{
14-
internal class JWT {
15-
internal string Sub { get; set; }
16-
}
1715

1816
internal class OptimisticPerformanceHandler : DelegatingHandler
1917
{
@@ -50,8 +48,8 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
5048
}
5149
var jwtPayload = token.Split('.')[1];
5250

53-
JWT jwt = JsonConvert.DeserializeObject<JWT>(Base64UrlEncoder.Decode(jwtPayload));
54-
var jwtSubject = jwt.Sub;
51+
var jwt = JsonConvert.DeserializeObject<JwtPayload>(Base64UrlEncoder.Decode(jwtPayload));
52+
var jwtSubject = jwt.Subject;
5553
if (string.IsNullOrEmpty(jwtSubject)) {
5654
return await base.SendAsync(request, cancellationToken);
5755
}

0 commit comments

Comments
 (0)