Skip to content

Commit

Permalink
Merge pull request #28 from wparad/add-verify-token
Browse files Browse the repository at this point in the history
Add VerifyToken method to the AuthressClient
  • Loading branch information
wparad authored Jan 22, 2024
2 parents b8d8e48 + a20718e commit ad03547
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ This is the changelog for [Authress SDK](readme.md).
## 2.0 ##
* Renamed `AccessRecordStatements` and other models that end with `S` but aren't actually plural to be `AccessRecordStatement` (without the `S`).
* All APIs are now part of sub instance properties of the `AuthressClient` class, `AccessClient.AccessRecords` and `AccessClient.ServiceClients`, etc..
* `ApiBasePath` has been renamed to `AuthressApiUrl`.
* `HttpClientSettings` Has been removed in favor of `AuthressSettings` Class.

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

## 1.4 ##
* Support exponential back-off retries on unexpected failures.
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ Installation:

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

#### Verify Authress JWT
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.

```csharp
using Authress.SDK;

// Get an authress custom domain: https://authress.io/app/#/settings?focus=domain
var authressSettings = new AuthressSettings { ApiBasePath = "https://authress.company.com", };
var authressClient = new AuthressClient(tokenProvider, authressSettings)

var verifiedUserIdentity = await authressClient.VerifyToken(jwtToken);
Console.WriteLine($"User ID: {verifiedUserIdentity.UserId}");
```

#### Authorize users using user identity token
```csharp
using Authress.SDK;
Expand All @@ -46,7 +60,7 @@ namespace Microservice
return accessToken;
});
// Get an authress custom domain: https://authress.io/app/#/settings?focus=domain
var authressSettings = new AuthressSettings { ApiBasePath = "https://CUSTOM_DOMAIN.application.com", };
var authressSettings = new AuthressSettings { ApiBasePath = "https://authress.company.com", };
var authressClient = new AuthressClient(tokenProvider, authressSettings);

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

// Attempt to Authorize the user for the resource
Expand Down
1 change: 1 addition & 0 deletions contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ Update the API Key: https://www.nuget.org/account/apikeys using the Rhosys Devel
* [ ] Remove LocalHost from the docs
* [ ] Tests
* [x] If-unmodified-since should called `expectedLastModifiedTime`, accept string or dateTime and convert this to an ISO String
* [ ] Update OAuth2 openapi authentication references in the documentation
24 changes: 15 additions & 9 deletions src/Authress.SDK/Client/AuthressClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Authress.SDK
public partial class AuthressClient
{
private readonly HttpClientProvider authressHttpClientProvider;
private readonly TokenVerifier tokenVerifier;

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

/// <summary>
Expand All @@ -38,17 +40,21 @@ public AuthressClient(ITokenProvider tokenProvider, HttpClientSettings settings,
throw new ArgumentNullException("Missing required parameter HttpClientSettings");
}
authressHttpClientProvider = new HttpClientProvider(
new AuthressSettings { ApiBasePath = settings?.ApiBasePath, RequestTimeout = settings?.RequestTimeout },
new AuthressSettings { ApiBasePath = settings.ApiBasePath, RequestTimeout = settings.RequestTimeout },
tokenProvider, customHttpClientHandlerFactory);
tokenVerifier = new TokenVerifier(settings.ApiBasePath, authressHttpClientProvider);
}

/// <summary>
/// Verify a JWT token from anywhere. If it is valid a VerifiedUserIdentity will be returned. If it is invalid an exception will be thrown.
/// </summary>
/// <param name="jwtToken"></param>
/// <returns>A verified user identity that contains the user's ID</returns>
/// <exception cref="TokenVerificationException">Token is invalid in some way</exception>
/// <exception cref="ArgumentNullException">One of the required parameters for this function was not specified.</exception>
public async Task<VerifiedUserIdentity> VerifyToken(string jwtToken) {
return await tokenVerifier.VerifyToken(jwtToken);
}
}

internal class AccessKey
{
public String Audience { get; set; }
public String ClientId { get; set; }
public String KeyId { get; set; }
public String PrivateKey { get; set; }
public String Algorithm { get; set; } = "RS256";
}
}
21 changes: 16 additions & 5 deletions src/Authress.SDK/Client/AuthressClientTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

namespace Authress.SDK
{
internal class AccessKey
{
public String Audience { get; set; }
public String ClientId { get; set; }
public String KeyId { get; set; }
public String PrivateKey { get; set; }
public String Algorithm { get; set; } = "RS256";
}

/// <summary>
/// Provides the token from locally stored access key. Access key can be retrieved when creating a new client in the Authress UI.
/// </summary>
Expand Down Expand Up @@ -47,8 +56,10 @@ public AuthressClientTokenProvider(string accessKeyBase64, string authressCustom
this.accessKey = new AccessKey
{
Algorithm = "EdDSA",
ClientId = accessKeyBase64.Split('.')[0], KeyId = accessKeyBase64.Split('.')[1],
Audience = $"{accountId}.accounts.authress.io", PrivateKey = accessKeyBase64.Split('.')[3]
ClientId = accessKeyBase64.Split('.')[0],
KeyId = accessKeyBase64.Split('.')[1],
Audience = $"{accountId}.accounts.authress.io",
PrivateKey = accessKeyBase64.Split('.')[3]
};

this.resolvedAuthressCustomDomain = (authressCustomDomain ?? $"{accountId}.api.authress.io").Replace("https://", "");
Expand Down Expand Up @@ -133,9 +144,9 @@ public Task<string> GetBearerToken(string authressCustomDomainFallback = null)
{
{ "iss", GetIssuer(authressCustomDomainFallback) },
{ "sub", accessKey.ClientId },
{ "exp", expiryDate.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
{ "iat", now.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
{ "nbf", now.Subtract(TimeSpan.FromMinutes(10)).Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds },
{ "exp", (int)Math.Floor(expiryDate.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
{ "iat", (int)Math.Floor(now.Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
{ "nbf", (int)Math.Floor(now.Subtract(TimeSpan.FromMinutes(10)).Subtract(new DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)).TotalSeconds) },
{ "aud", accessKey.Audience },
{ "scopes", "openid" }
};
Expand Down
10 changes: 5 additions & 5 deletions src/Authress.SDK/Client/HttpClientProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ public interface IHttpClientHandlerFactory
}

/// <summary>
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// </summary>
public class HttpClientSettings
{
/// <summary>
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// </summary>
public string ApiBasePath { get; set; } = "https://api.authress.io";

Expand All @@ -33,12 +33,12 @@ public class HttpClientSettings
}

/// <summary>
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// </summary>
public class AuthressSettings
{
/// <summary>
/// Authress Domain Host: https://CUSTOM_DOMAIN.application.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// Authress Domain Host: https://authress.company.com (Get an authress custom domain: https://authress.io/app/#/settings?focus=domain)
/// </summary>
public string ApiBasePath { get; set; } = "https://api.authress.io";

Expand Down Expand Up @@ -68,7 +68,7 @@ internal class HttpClientProvider

public HttpClientProvider(AuthressSettings settings, ITokenProvider tokenProvider, IHttpClientHandlerFactory customHttpClientHandlerFactory = null)
{
this.settings = settings;
this.settings = settings ?? new AuthressSettings();
this.tokenProvider = tokenProvider;
this.customHttpClientHandlerFactory = customHttpClientHandlerFactory;
}
Expand Down
30 changes: 30 additions & 0 deletions src/Authress.SDK/Client/JWT/JwtPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Authress.SDK.Client.JWT
{
[DataContract]
internal class JwtPayload
{

[DataMember(Name = "sub")]
internal string Subject { get; set; }

[DataMember(Name = "iss")]
internal string Issuer { get; set; }

[DataMember(Name = "exp")]
[JsonConverter(typeof(UnixDateTimeConverter))]
internal DateTimeOffset Expires { get; set; }
}

[DataContract]
internal class JwtHeader
{

[DataMember(Name = "kid")]
internal string KeyId { get; set; }
}
}
8 changes: 3 additions & 5 deletions src/Authress.SDK/Client/OptimisticPerformanceHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Authress.SDK.Client.JWT;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace Authress.SDK.Client
{
internal class JWT {
internal string Sub { get; set; }
}

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

JWT jwt = JsonConvert.DeserializeObject<JWT>(Base64UrlEncoder.Decode(jwtPayload));
var jwtSubject = jwt.Sub;
var jwt = JsonConvert.DeserializeObject<JwtPayload>(Base64UrlEncoder.Decode(jwtPayload));
var jwtSubject = jwt.Subject;
if (string.IsNullOrEmpty(jwtSubject)) {
return await base.SendAsync(request, cancellationToken);
}
Expand Down
Loading

0 comments on commit ad03547

Please sign in to comment.