Repository navigation
OIDC resource server: scheme, configuration and Protected Resource Metadata (1/7) #891
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
70833fb
OIDC resource server: configuration and a protected resource registry…
KrzysztofPajak 0f66cdd
OIDC resource server: canonical resource URIs, audience, authorized p…
KrzysztofPajak cfd1888
OIDC resource server: RFC 6750 challenges and RFC 9728 protected reso…
KrzysztofPajak e4fe29d
OIDC resource server: Oidc JwtBearer scheme, startup validation and m…
KrzysztofPajak ef88aee
OIDC resource server: pipeline tests with an in-memory signing key
KrzysztofPajak 7b1cc26
OIDC resource server: store URL as audience base, fail-closed account…
KrzysztofPajak 6768e2b
OIDC resource server: always write the ProblemDetails body of challen…
KrzysztofPajak db6434f
OIDC resource server: resolve the audience store from the request hos…
KrzysztofPajak f51fb29
OIDC resource server: RFC 9728 metadata URL with the well-known segme…
KrzysztofPajak c611acd
OIDC resource server: 404 and a warning instead of a metadata documen…
KrzysztofPajak 7f1a4a8
Skip store and work context setup on the protected resource metadata …
KrzysztofPajak 30741b7
OIDC resource server: answer 503 instead of invalid_token when the au…
KrzysztofPajak 1101972
OIDC resource server: treat a negative ClockSkewSeconds as zero inste…
KrzysztofPajak c0d3952
OIDC resource server: build the registry's read-only resource list once
KrzysztofPajak ea2c20e
OIDC resource server: leave enablement of the metadata endpoints to t…
KrzysztofPajak 5af62c3
OIDC resource server tests: dispose request messages and RSA instances
KrzysztofPajak e084407
OIDC resource server: filter explicitly in the header escaping and re…
KrzysztofPajak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/Business/Grand.Business.Core/Interfaces/Authentication/IOidcProtectedResourceProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using Grand.Infrastructure.Configuration; | ||
|
|
||
| namespace Grand.Business.Core.Interfaces.Authentication; | ||
|
|
||
| /// <summary> | ||
| /// Lets a plugin declare the resources it protects with access tokens of the identity provider. Register the | ||
| /// implementation as a singleton in the plugin's IStartupApplication and protect the endpoints with | ||
| /// [Authorize(AuthenticationSchemes = "Oidc")]. | ||
| /// </summary> | ||
| public interface IOidcProtectedResourceProvider | ||
| { | ||
| IEnumerable<OidcProtectedResource> GetResources(); | ||
| } |
16 changes: 16 additions & 0 deletions
16
src/Business/Grand.Business.Core/Utilities/Authentication/OidcFailureKind.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| namespace Grand.Business.Core.Utilities.Authentication; | ||
|
|
||
| public enum OidcFailureKind | ||
| { | ||
| /// <summary>401 error="invalid_token"</summary> | ||
| InvalidToken, | ||
|
|
||
| /// <summary>403 error="insufficient_scope"</summary> | ||
| InsufficientScope, | ||
|
|
||
| /// <summary>403 without a bearer error</summary> | ||
| Forbidden, | ||
|
|
||
| /// <summary>503 without a bearer error: the authorization server's metadata or signing keys cannot be obtained</summary> | ||
| ServiceUnavailable | ||
| } |
31 changes: 31 additions & 0 deletions
31
src/Core/Grand.Infrastructure/Configuration/OidcProtectedResource.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| namespace Grand.Infrastructure.Configuration; | ||
|
|
||
| /// <summary> | ||
| /// An API area protected by access tokens of the identity provider | ||
| /// </summary> | ||
| public class OidcProtectedResource | ||
| { | ||
| /// <summary>Unique name, also published as resource_name</summary> | ||
| public string Name { get; set; } | ||
|
|
||
| /// <summary>Path prefix, matched case-insensitively on a segment boundary ("/api" covers "/api/x", not "/apix")</summary> | ||
| public string PathPrefix { get; set; } | ||
|
|
||
| /// <summary>Canonical resource URI (RFC 8707); empty = the request's base URL + PathPrefix</summary> | ||
| public string Resource { get; set; } | ||
|
|
||
| /// <summary>Additional accepted "aud" values, for providers that do not put the resource URI there</summary> | ||
| public List<string> Audiences { get; set; } = []; | ||
|
|
||
| /// <summary>When not empty, "azp" (or "client_id") must be one of these</summary> | ||
| public List<string> AuthorizedParties { get; set; } = []; | ||
|
|
||
| public List<string> ScopesSupported { get; set; } = []; | ||
| public List<string> RequiredScopes { get; set; } = []; | ||
|
|
||
| /// <summary>The customer needs the admin panel access permission</summary> | ||
| public bool RequireAdminPanelAccess { get; set; } | ||
|
|
||
| public bool DenyVendors { get; set; } | ||
| public bool DenyStoreManagers { get; set; } | ||
| } |
38 changes: 38 additions & 0 deletions
38
src/Core/Grand.Infrastructure/Configuration/OidcResourceServerConfig.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| namespace Grand.Infrastructure.Configuration; | ||
|
|
||
| /// <summary> | ||
| /// OAuth 2.1 / OpenID Connect resource server: accepts access tokens issued by an external identity provider | ||
| /// on the protected resources listed in <see cref="Resources" /> or registered by plugins | ||
| /// </summary> | ||
| public class OidcResourceServerConfig | ||
| { | ||
| public bool Enabled { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Issuer of the identity provider; discovery document at {Authority}/.well-known/openid-configuration | ||
| /// </summary> | ||
| public string Authority { get; set; } | ||
|
|
||
| public bool RequireHttpsMetadata { get; set; } = true; | ||
| public int ClockSkewSeconds { get; set; } = 60; | ||
| public OidcClaimNames Claims { get; set; } = new(); | ||
|
|
||
| /// <summary> | ||
| /// An unverified e-mail address never links to an account and never registers one | ||
| /// </summary> | ||
| public bool RequireEmailVerified { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// Register an unknown user (customer group Registered only) | ||
| /// </summary> | ||
| public bool AutoRegister { get; set; } = true; | ||
|
|
||
| public List<OidcProtectedResource> Resources { get; set; } = []; | ||
| } | ||
|
|
||
| public class OidcClaimNames | ||
| { | ||
| public string Subject { get; set; } = "sub"; | ||
| public string Email { get; set; } = "email"; | ||
| public string EmailVerified { get; set; } = "email_verified"; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
src/Tests/Grand.Web.Common.Tests/Middleware/ContextMiddlewareTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| using Grand.Domain.Stores; | ||
| using Grand.Infrastructure; | ||
| using Grand.Web.Common.Middleware; | ||
| using Grand.Web.Common.Security.Oidc; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Routing; | ||
| using Microsoft.AspNetCore.Routing.Patterns; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Moq; | ||
|
|
||
| namespace Grand.Web.Common.Tests.Middleware; | ||
|
|
||
| [TestClass] | ||
| public class ContextMiddlewareTests | ||
| { | ||
| private Mock<IStoreContextSetter> _storeContextSetter; | ||
| private Mock<IWorkContextSetter> _workContextSetter; | ||
|
|
||
| private async Task<bool> Invoke(string routePattern) | ||
| { | ||
| _storeContextSetter = new Mock<IStoreContextSetter>(); | ||
| var storeContext = new Mock<IStoreContext>(); | ||
| storeContext.Setup(x => x.CurrentStore).Returns(new Store { Id = "store1" }); | ||
| _storeContextSetter.Setup(x => x.InitializeStoreContext(It.IsAny<string>())).ReturnsAsync(storeContext.Object); | ||
| _workContextSetter = new Mock<IWorkContextSetter>(); | ||
|
|
||
| var services = new ServiceCollection(); | ||
| services.AddSingleton(_storeContextSetter.Object); | ||
| services.AddSingleton(_workContextSetter.Object); | ||
| var http = new DefaultHttpContext { RequestServices = services.BuildServiceProvider() }; | ||
| http.SetEndpoint(new RouteEndpoint(_ => Task.CompletedTask, RoutePatternFactory.Parse(routePattern), 0, | ||
| EndpointMetadataCollection.Empty, null)); | ||
|
|
||
| var nextCalled = false; | ||
| var middleware = new ContextMiddleware(_ => | ||
| { | ||
| nextCalled = true; | ||
| return Task.CompletedTask; | ||
| }); | ||
| var contextAccessor = new Mock<IContextAccessor>(); | ||
| contextAccessor.SetupAllProperties(); | ||
| await middleware.InvokeAsync(http, contextAccessor.Object); | ||
| return nextCalled; | ||
| } | ||
|
|
||
| [TestMethod] | ||
| [DataRow(OidcProtectedResourceMetadata.WellKnownPath)] | ||
| [DataRow(OidcProtectedResourceMetadata.WellKnownPath + "/{**path}")] | ||
| public async Task ProtectedResourceMetadata_NoStoreOrWorkContext(string routePattern) | ||
| { | ||
| Assert.IsTrue(await Invoke(routePattern)); | ||
| _storeContextSetter.Verify(x => x.InitializeStoreContext(It.IsAny<string>()), Times.Never); | ||
| _workContextSetter.Verify(x => x.InitializeWorkContext(It.IsAny<string>()), Times.Never); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task OtherRoute_InitializesStoreAndWorkContext() | ||
| { | ||
| Assert.IsTrue(await Invoke("/api/{controller}")); | ||
| _workContextSetter.Verify(x => x.InitializeWorkContext("store1"), Times.Once); | ||
| } | ||
| } |
44 changes: 44 additions & 0 deletions
44
src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcCanonicalUriTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| using Grand.Infrastructure.Configuration; | ||
| using Grand.Web.Common.Security.Oidc; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| namespace Grand.Web.Common.Tests.Security.Oidc; | ||
|
|
||
| [TestClass] | ||
| public class OidcCanonicalUriTests | ||
| { | ||
| [TestMethod] | ||
| public void Derived_FromBaseUrlAndPrefix() => | ||
| Assert.AreEqual("https://shop.example.com/eu/api", | ||
| OidcCanonicalUri.ResourceFor(new OidcProtectedResource { PathPrefix = "/api/" }, "https://shop.example.com/eu")); | ||
|
|
||
| [TestMethod] | ||
| public void Derived_KeepsNonDefaultPort_LowercasesHost() => | ||
| Assert.AreEqual("https://shop.example.com:8443/api", | ||
| OidcCanonicalUri.ResourceFor(new OidcProtectedResource { PathPrefix = "/api" }, "https://Shop.Example.com:8443")); | ||
|
|
||
| [TestMethod] | ||
| public void Configured_WinsAndIsNormalized() => | ||
| Assert.AreEqual("https://api.example.com/v1", | ||
| OidcCanonicalUri.ResourceFor(new OidcProtectedResource { PathPrefix = "/api", Resource = "HTTPS://API.Example.com:443/v1/" }, "https://shop.example.com")); | ||
|
|
||
| [TestMethod] | ||
| public void Derived_NoBaseUrl_Null() => | ||
| Assert.IsNull(OidcCanonicalUri.ResourceFor(new OidcProtectedResource { PathPrefix = "/api" }, null)); | ||
|
|
||
| [TestMethod] | ||
| [DataRow("https://shop.example.com/api", "https://shop.example.com/api")] | ||
| [DataRow("https://Shop.Example.com/api/", "https://shop.example.com/api")] | ||
| [DataRow("HTTPS://shop.example.com:443/api", "https://shop.example.com/api")] | ||
| [DataRow("grandnode-api", "grandnode-api")] | ||
| public void Equal_True(string a, string b) => Assert.IsTrue(OidcCanonicalUri.Equal(a, b)); | ||
|
|
||
| [TestMethod] | ||
| [DataRow("https://shop.example.com/api2", "https://shop.example.com/api")] | ||
| [DataRow("https://shop.example.com/API", "https://shop.example.com/api")] | ||
| [DataRow("http://shop.example.com/api", "https://shop.example.com/api")] | ||
| [DataRow("https://shop.example.com:8443/api", "https://shop.example.com/api")] | ||
| [DataRow("Grandnode-api", "grandnode-api")] | ||
| [DataRow(null, "grandnode-api")] | ||
| public void Equal_False(string a, string b) => Assert.IsFalse(OidcCanonicalUri.Equal(a, b)); | ||
| } |
86 changes: 86 additions & 0 deletions
86
src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcChallengeWriterTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| using Grand.Business.Core.Utilities.Authentication; | ||
| using Grand.Web.Common.Security.Oidc; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| namespace Grand.Web.Common.Tests.Security.Oidc; | ||
|
|
||
| [TestClass] | ||
| public class OidcChallengeWriterTests | ||
| { | ||
| private const string Meta = "https://shop.example.com/.well-known/oauth-protected-resource/api"; | ||
|
|
||
| [TestMethod] | ||
| public void NoFailure_MetadataAndScope() => | ||
| Assert.AreEqual($"Bearer resource_metadata=\"{Meta}\", scope=\"grandnode.api\"", | ||
| OidcChallengeWriter.BuildHeader(null, Meta, "grandnode.api")); | ||
|
|
||
| [TestMethod] | ||
| public void NoFailure_NoScope() => | ||
| Assert.AreEqual($"Bearer resource_metadata=\"{Meta}\"", OidcChallengeWriter.BuildHeader(null, Meta, null)); | ||
|
|
||
| [TestMethod] | ||
| public void InvalidToken() => | ||
| Assert.AreEqual($"Bearer error=\"invalid_token\", error_description=\"The token has expired\", resource_metadata=\"{Meta}\"", | ||
| OidcChallengeWriter.BuildHeader( | ||
| new OidcAuthenticationFailure { Kind = OidcFailureKind.InvalidToken, Description = "The token has expired" }, Meta, "ignored")); | ||
|
|
||
| [TestMethod] | ||
| public void InsufficientScope_UsesFailureScope() => | ||
| Assert.AreEqual($"Bearer error=\"insufficient_scope\", error_description=\"The token lacks a required scope\", scope=\"a b\", resource_metadata=\"{Meta}\"", | ||
| OidcChallengeWriter.BuildHeader(new OidcAuthenticationFailure { | ||
| Kind = OidcFailureKind.InsufficientScope, Description = "The token lacks a required scope", Scope = "a b" | ||
| }, Meta, "ignored")); | ||
|
|
||
| [TestMethod] | ||
| public void QuotesEscaped_ControlCharactersDropped() => | ||
| Assert.AreEqual("Bearer error=\"invalid_token\", error_description=\"a\\\"b\\\\cd\"", | ||
| OidcChallengeWriter.BuildHeader( | ||
| new OidcAuthenticationFailure { Kind = OidcFailureKind.InvalidToken, Description = "a\"b\\c\r\nd" }, null, null)); | ||
|
|
||
| private static DefaultHttpContext Context() | ||
| { | ||
| var context = new DefaultHttpContext { RequestServices = new ServiceCollection().BuildServiceProvider() }; | ||
| context.Response.Body = new MemoryStream(); | ||
| return context; | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task Write_Forbidden_403_NoHeader_ProblemJson() | ||
| { | ||
| var context = Context(); | ||
| await OidcChallengeWriter.Write(context, | ||
| new OidcAuthenticationFailure { Kind = OidcFailureKind.Forbidden, Description = "The customer account is inactive" }, Meta, null); | ||
|
|
||
| Assert.AreEqual(403, context.Response.StatusCode); | ||
| Assert.IsFalse(context.Response.Headers.ContainsKey("WWW-Authenticate")); | ||
| StringAssert.StartsWith(context.Response.ContentType, "application/problem+json"); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task Write_NoFailure_401_WithHeader() | ||
| { | ||
| var context = Context(); | ||
| await OidcChallengeWriter.Write(context, null, Meta, "grandnode.api"); | ||
|
|
||
| Assert.AreEqual(401, context.Response.StatusCode); | ||
| StringAssert.Contains(context.Response.Headers.WWWAuthenticate.ToString(), "resource_metadata"); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task Write_ServiceUnavailable_503_NoBearerError_ProblemJson() | ||
| { | ||
| var context = Context(); | ||
| await OidcChallengeWriter.Write(context, | ||
| new OidcAuthenticationFailure { Kind = OidcFailureKind.ServiceUnavailable, Description = "The authorization server is unavailable" }, Meta, null); | ||
|
|
||
| Assert.AreEqual(503, context.Response.StatusCode); | ||
| Assert.IsFalse(context.Response.Headers.ContainsKey("WWW-Authenticate")); | ||
| StringAssert.StartsWith(context.Response.ContentType, "application/problem+json"); | ||
| context.Response.Body.Position = 0; | ||
| var body = await new StreamReader(context.Response.Body).ReadToEndAsync(); | ||
| StringAssert.Contains(body, "\"status\":503"); | ||
| StringAssert.Contains(body, "\"title\":\"Service unavailable\""); | ||
| } | ||
| } | ||
76 changes: 76 additions & 0 deletions
76
src/Tests/Grand.Web.Common.Tests/Security/Oidc/OidcMvcProblemDetailsTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| using Microsoft.AspNetCore.Authorization; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using System.Net.Http.Headers; | ||
| using System.Text.Json; | ||
|
|
||
| namespace Grand.Web.Common.Tests.Security.Oidc; | ||
|
|
||
| /// <summary> | ||
| /// Challenges and refusals on MVC controller endpoints, registered like the app does (MVC before AddProblemDetails) | ||
| /// </summary> | ||
| [TestClass] | ||
| public class OidcMvcProblemDetailsTests | ||
| { | ||
| private OidcTestHost _host; | ||
| private HttpClient _client; | ||
|
|
||
| [TestInitialize] | ||
| public async Task Init() | ||
| { | ||
| _host = new OidcTestHost(); | ||
| _client = await _host.Start(OidcTestHost.Config(), | ||
| s => s.AddControllers().AddApplicationPart(typeof(OidcProbeController).Assembly), | ||
| endpoints: e => e.MapControllers()); | ||
| } | ||
|
|
||
| [TestCleanup] | ||
| public async Task Cleanup() => await _host.DisposeAsync(); | ||
|
|
||
| [TestMethod] | ||
| [DataRow("/api/probe", null, 401)] | ||
| [DataRow("/api/probe", "not-a-token", 401)] | ||
| [DataRow("/api/probe", "scope:other", 403)] | ||
| [DataRow("/api/apiprobe", null, 401)] | ||
| [DataRow("/api/apiprobe", "scope:other", 403)] | ||
| public async Task ChallengeAndRefusal_WriteProblemDetailsBody(string path, string token, int status) | ||
| { | ||
| using var request = new HttpRequestMessage(HttpMethod.Get, path); | ||
| request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
| if (token != null) | ||
| request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", | ||
| token == "scope:other" ? OidcTestHost.Token(scope: "other") : token); | ||
|
|
||
| using var response = await _client.SendAsync(request); | ||
| var body = await response.Content.ReadAsStringAsync(); | ||
|
|
||
| Assert.AreEqual(status, (int)response.StatusCode); | ||
| Assert.AreEqual("application/problem+json", response.Content.Headers.ContentType?.MediaType, body); | ||
| using var json = JsonDocument.Parse(body); | ||
| Assert.AreEqual(status, json.RootElement.GetProperty("status").GetInt32()); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Like the Web API controllers: a controller without [ApiController] | ||
| /// </summary> | ||
| [Authorize(AuthenticationSchemes = "Oidc")] | ||
| [Route("api/probe")] | ||
| [Produces("application/json")] | ||
| public class OidcProbeController : ControllerBase | ||
| { | ||
| [HttpGet] | ||
| public string Get() => "ok"; | ||
| } | ||
|
|
||
| [ApiController] | ||
| [Authorize(AuthenticationSchemes = "Oidc")] | ||
| [Route("api/apiprobe")] | ||
| [Produces("application/json")] | ||
| public class OidcApiProbeController : ControllerBase | ||
| { | ||
| [HttpGet] | ||
| public string Get() => "ok"; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.