Skip to content

Commit

Permalink
Implementing_Custom_Scheme_Handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Shoogn committed Sep 29, 2023
1 parent f1d663d commit 67c27b8
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 23 deletions.
12 changes: 4 additions & 8 deletions ClientApp_OpenId/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ namespace OpenIdTestApp.Controllers

public class HomeController : Controller
{
// [Authorize]
public async Task<IActionResult> Index()
[Authorize]
public IActionResult Index()
{
string t3 = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6Imp3dGFwaXRlc3RhcHAucmVhZCIsImV4cCI6MTY5NTkwMDAyMSwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NzI3NSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjcyNzYifQ.tET_-uTdERrwj2YFwNyH8Re16ec67rfzNpNj2IWHX0fVDneMo6acadgnFqLz4FtwZkox-IHA90TRj713QlTQ1UhTpuQHGm-76Ro5HbniBd6fow18s4PMCTyJsLtYSOyh1kNmtpO76deSDu1czXHM0pK-YbgFTfpTrFE_o-z7P8Q";
bool y = await TokenIntrospection(t3 ?? "");
return View();
}

Expand All @@ -21,7 +19,7 @@ public async Task<IActionResult> Index(string val)
{
string? access_token = await getToken();
string? res = await getDataFromApi(access_token ?? "");
return View(res);
return View("Index", res);
}

private async Task<string?> getToken()
Expand Down Expand Up @@ -94,7 +92,7 @@ public class OAuthCallingResult
}
}


// For reference only
private async Task<bool> TokenIntrospection(string token)
{
using (var client = new HttpClient())
Expand Down Expand Up @@ -132,8 +130,6 @@ private async Task<bool> TokenIntrospection(string token)
// res.EnsureSuccessStatusCode(); // If not success it will throw HttpRequestException
string responseBody = await res.Content.ReadAsStringAsync();


//var result = System.Text.Json.JsonSerializer.Deserialize<OAuthCallingResult>(responseBody);
return true;
}
}
Expand Down
5 changes: 5 additions & 0 deletions OAuth20.Server/Common/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,10 @@ public static bool IsRedirectUriStartWithHttps(this string redirectUri)

return false;
}

public static bool StringIsNullOrWhiteSpace(this string value)
{
return string.IsNullOrWhiteSpace(value);
}
}
}
3 changes: 2 additions & 1 deletion OAuth20.Server/Controllers/DiscoveryEndpointController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public JsonResult GetConfiguration()
"locale", "zoneinfo" },
claims_parameter_supported = true,
service_documentation = "https://localhost:7275/connect/service_documentation.html",
ui_locales_supported = new string[] { "en-US", "en-GB", "en-CA", "fr-FR", "fr-CA" }
ui_locales_supported = new string[] { "en-US", "en-GB", "en-CA", "fr-FR", "fr-CA" },
introspection_endpoint = "https://localhost:7275/Introspections/TokenIntrospect"

};

Expand Down
1 change: 1 addition & 0 deletions OAuth20.Server/Endpoints/DiscoveryResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ public class DiscoveryResponse
public bool claims_parameter_supported { get; set; }
public string service_documentation { get; set; }
public IList<string> ui_locales_supported { get; set; }
public string introspection_endpoint { get; set; }
}
}
2 changes: 1 addition & 1 deletion OAuth20.Server/OauthRequest/TokenIntrospectionRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class TokenIntrospectionRequest
/// <summary>
/// Get or set token type hint.
/// </summary>

[JsonInclude]
[JsonPropertyName("token_type_hint")]
public string TokenTypeHint { get; set; }
}
Expand Down
2 changes: 1 addition & 1 deletion OAuth20.Server/OauthRequest/TokenRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public class TokenRequest
public string grant_type { get; set; }
public string redirect_uri { get; set; }
public string code_verifier { get; set; }
public IList<string> scopes { get; set; }
public IList<string> scope { get; set; }
}
}
2 changes: 1 addition & 1 deletion OAuth20.Server/Services/AuthorizeResultService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public TokenResponse GenerateToken(TokenRequest tokenRequest)
result.Error = ErrorTypeEnum.InvalidGrant.GetEnumDescription();
return result;
}
IEnumerable<string> scopes = checkClientResult.Client.AllowedScopes.Intersect(tokenRequest.scopes);
IEnumerable<string> scopes = checkClientResult.Client.AllowedScopes.Intersect(tokenRequest.scope);

var clientCredentialAccessTokenResult = generateJWTTokne(scopes, Constants.TokenTypes.JWTAcceseccToken, checkClientResult.Client);
SaveJWTTokenInBackStore(checkClientResult.Client.ClientId, clientCredentialAccessTokenResult.AccessToken, clientCredentialAccessTokenResult.ExpirationDate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ArticlesController : ControllerBase
[HttpGet("GetDummyData")]
public IActionResult GetDummyData()
{
string result = "Calling to this endpoint at https://localhost:7065 is done sucssefully";
string result = "Calling to this endpoint at https://localhost:7065 is done successfully";
return Ok(result);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Authentication;

namespace ProtectedResourceApp_JwtBearer.Infrastructure
{
public class SendingTokenIntrospectionRequestContext : BaseContext<OAuth2ServerOptions>
{
public SendingTokenIntrospectionRequestContext(HttpContext context,
AuthenticationScheme scheme,
OAuth2ServerOptions options)
: base(context, scheme, options)
{
}

/// <summary>
/// Get or set token;
/// </summary>
public string Token { get; set; } = default!;

/// <summary>
/// Get or set token type hint.
/// </summary>
public string TokenTypeHint { get; set; } = default!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authentication;

namespace ProtectedResourceApp_JwtBearer.Infrastructure
{
/// <summary>
/// Define the context for validated token.
/// </summary>
public class TokenValidatedContext : ResultContext<OAuth2ServerOptions>
{
public TokenValidatedContext(HttpContext context,
AuthenticationScheme scheme,
OAuth2ServerOptions options)
: base(context, scheme, options)
{
}

/// <summary>
/// Get or set the token;
/// </summary>
public string Token { get; set; } = default!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace ProtectedResourceApp_JwtBearer.Infrastructure
{
/// <summary>
/// Define default value to use in the <see cref="OAuth2ServerHandler"/> for JWT bearer authentication.
/// </summary>
public static class OAuth2IntrospectionJwtBearerDefaults
{
/// <summary>
/// The default authentication scheme.
/// </summary>
public const string AuthenticationScheme = "Bearer";

/// <summary>
/// HttpClient name, that will be resolved from HttpClientFactory.
/// </summary>
public const string NamedBackChannelHttpClient = "OAuth2BackChannelHttpClient";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Authentication;

namespace ProtectedResourceApp_JwtBearer.Infrastructure.OAuth2Scheme
{
public static class OAuth2JwtBearerExtensions
{
public static AuthenticationBuilder AddOAuth2IntrospectionJwtBearer(this AuthenticationBuilder builder,
string authenticationSchem,Action<OAuth2ServerOptions> configureOptions)
{
builder.Services.AddHttpClient(OAuth2IntrospectionJwtBearerDefaults.NamedBackChannelHttpClient);

// The configureOptions will registerd by defult internaly if is not null in a method named AddSchemeHelper( ... parameters here ... );

return builder.AddScheme< OAuth2ServerOptions, OAuth2ServerHandler>(authenticationSchem, configureOptions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using ProtectedResourceApp_JwtBearer.Infrastructure.OAuth2Scheme;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace ProtectedResourceApp_JwtBearer.Infrastructure
{
// Made in love by Mohammed Ahmed Hussien
public class OAuth2ServerHandler : AuthenticationHandler<OAuth2ServerOptions>
{
private readonly IHttpClientFactory _httpClientFactory;
public OAuth2ServerHandler(IOptionsMonitor<OAuth2ServerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IHttpClientFactory httpClientFactory)
: base(options, logger, encoder, clock)
{
_httpClientFactory = httpClientFactory;
}

protected new OAuth2TokenIntrospectionEvent Events
{
get => (OAuth2TokenIntrospectionEvent)base.Events!;
set => base.Events = value;
}

protected override Task<object> CreateEventsAsync()
{
return Task.FromResult<object>(new OAuth2TokenIntrospectionEvent());
}


protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = string.Empty;
try
{
string authorization = Request.Headers.Authorization.ToString();
if (string.IsNullOrWhiteSpace(authorization))
{
return AuthenticateResult.NoResult();
}

if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}

if (string.IsNullOrWhiteSpace(token))
{
return AuthenticateResult.NoResult();
}

var requestSendingContext = new SendingTokenIntrospectionRequestContext(Context, Scheme, Options)
{
Token = token,
TokenTypeHint = Options.TokenTypeHint
};
await Events.SendingTokenIntrospectionRequest(requestSendingContext);


var client = _httpClientFactory.CreateClient(OAuth2IntrospectionJwtBearerDefaults.NamedBackChannelHttpClient);
var values = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("token", token),
new KeyValuePair<string, string>("token_type_hint", Options.TokenTypeHint)
};

Uri baseUri = new Uri(Options.Authority);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
string credential = String.Format("{0}:{1}", Options.ClientId, Options.ClientSecret);
string parameters = Convert.ToBase64String(Encoding.UTF8.GetBytes(credential));
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", parameters);
using var req = new HttpRequestMessage(HttpMethod.Post, "/Introspections/TokenIntrospect") { Content = new FormUrlEncodedContent(values) };
using var res = await client.SendAsync(req);

if (res.IsSuccessStatusCode == false)
{
return AuthenticateResult.Fail($"Calling introspection endpoint is faild with this status code: {res.StatusCode}");
}


string responseBody = await res.Content.ReadAsStringAsync();
TokenIntrospectionResponse? result = System.Text.Json.JsonSerializer.Deserialize<TokenIntrospectionResponse>(
responseBody, new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });


if (result?.Active ?? false)
{
// Create ticket
var authenticationType = Options.AuthenticationType ?? Scheme.Name;
var claimIdentity = new ClaimsIdentity(result.Claims, authenticationType, "name", "role");
var claimPrinciple = new ClaimsPrincipal(claimIdentity);

// TODO: here I need token vaidation context
TokenValidatedContext tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = claimPrinciple,
Token = token
};
await Events.TokenValidated(tokenValidatedContext);

if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}

if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result!;
}
else
{
return AuthenticateResult.Fail($"The token is not active");
}
}
catch (Exception ex)
{
return AuthenticateResult.Fail($"There is an exception {ex}");
}





}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Authentication;

namespace ProtectedResourceApp_JwtBearer.Infrastructure
{
public class OAuth2ServerOptions : AuthenticationSchemeOptions
{
/// <summary>
/// Get or set the OAuthrization URI.
/// </summary>
public string Authority { get; set; } = default!;

/// <summary>
/// Get or set the client Id.
/// </summary>
public string ClientId { get; set; } = default!;

/// <summary>
/// Get or set the client secret.
/// </summary>
public string ClientSecret { get; set; } = default!;

// <summary>
// Defines whether the bearer token should be stored in the
// Microsoft.AspNetCore.Authentication.AuthenticationProperties
// after a successful authorization.
// </summary>
public bool SaveToken { get; set; }

/// <summary>
/// Gets or sets the challenge to put in the "WWW-Authenticate" header.
/// </summary>
public string Challenge { get; set; } = "Bearer";

/// <summary>
/// The Backchannel used to retrieve metadata.
/// </summary>
public HttpClient BackChannel { get; set; } = default!;

/// <summary>
/// Get or set token type hint of the introspection client.
/// </summary>
public string TokenTypeHint { get; set; } = "access_token";

/// <summary>
/// Set the authentication type for the authenticated identity, null by default.
/// </summary>
public string? AuthenticationType { get; set; }
/// <summary>
/// Get or set the discovery endpoint to retrive all informations about OAuth2 server.
/// </summary>
//public string MetadataAddress { get; set; } = default!;

/// <summary>
/// Get or set the required scheme type for <see cref="MetadataAddress"/>
/// </summary>
// public bool RequireHttpsMetadata { get; set; }
}
}
Loading

0 comments on commit 67c27b8

Please sign in to comment.