How to document Form-based Authemtication implemented by spring security via spring-doc-openapi? - forms-authentication

I'm implementing auth for my REST api and I have a problecm with docs. I wrote auth by spring-security form-based authentication. On success it gives me coockie to keep seesion authenticated. I have found no way to make spring-doc-openapi to find default spring-security controller for login/logout operations. The only way i found - make fakes of them like this:
#RestController
#RequestMapping(value = "/")
public class FakeSecurityController {
/**
* Implemented by Spring Security
*/
#Operation(summary = "Login with the given credentials.")
#ApiResponses(value = {
#ApiResponse(responseCode = "200",
description = "Login processing url",
content = #Content(schema = #Schema(implementation = AuthDTO.class)),
headers = #Header(name = "SESSION", description = "Cookie", required = true, schema = #Schema(implementation = String.class)))
})
#PostMapping(value = "/login", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<AuthDTO> login(#RequestParam("username") String username,
#RequestParam("password") String password) {
throw new IllegalStateException("Add Spring Security to handle authentication");
}
/**
* Implemented by Spring Security
*/
#Operation(summary = "Logout the current user.")
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Logout processing url")
})
#PostMapping(value = "/logout")
#SecurityRequirements
public void logout() {
throw new IllegalStateException("Add Spring Security to handle authentication");
}
}
What should i do to make spring-doc-openapi doceument these endpoints by itself?

If your project that uses spring-security, you should add the following dependency, in combination with the springdoc-openapi-ui dependency: This dependency helps ignoring #AuthenticationPrincipal and provide the spring-security /login endpoint out of the box:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>${springdoc.version}</version>
</dependency>

Related

id_token_hint parameter failed signature validation when Using B2C to generate the metadata endpoints

I am attempting to set up a Magic link like system using Azure B2C. Using the following samples:
Primary:
https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-magic-link
For sing B2C to generate the metadata endpoints:
https://github.com/azure-ad-b2c/samples/tree/master/policies/invite#using-b2c-to-generate-the-metadata-endpoints
As a note I believe I had it working at one point but after a clean up I have been getting the error:
The provided id_token_hint parameter failed signature validation. Please provide another token and try again.
The steps I took to set up is as follows:
Create a cert via powershell and get thumbprint to use in local code
Use certmng via MMC to export cert
All Task / Export / Next / Yes, Export the private key
Personal Information Exchange - PKCS (Include all cert in cert path)(Enable cert privacy)
Security (Password) Randomly Generated Pass 25 character password.
Name: id_token_hint_cert.pfx
Browse Azure / B2C / Identity Experience Framework / Policy keys
Add / Option: Upload / Name: IdTokenHintCert / File Upload id_token_hint_cert.pfx / Password: Password from setup 3
This is where I have tried 2 different set ups. The first was to setup a set of custom policies so that I could update the following claims provider to have issuer_secret set to B2C_1A_IdTokenHintCert
<ClaimsProvider>
<DisplayName>Token Issuer</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="JwtIssuer">
<DisplayName>JWT Issuer</DisplayName>
<Protocol Name="None" />
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="client_id">{service:te}</Item>
<Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>
<Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_IdTokenHintCert" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
<InputClaims />
<OutputClaims />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
This is set of policies grabbed from https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/tree/master/LocalAccounts and updated to my tenant but left mostly alone.
I also tried changing out the issuer_secret in my main custom policies with the same error being output.
Heading into my code:
This is the important part of my startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
OpenIdConnectDefaults.AuthenticationScheme
).AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"),"OpenIdConnect", "Cookies",true);
services.AddControllersWithViews();
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddTransient<IClaimsTransformation, AppClaimsTransformations>();
}
And here is my Home Controller where I submit a form, create the token and link and then just redirect to that link using it as a run now endpoint. (I know this wont end of working but need to get the signature validate before I can move on.)
public class HomeController : Controller
{
private static Lazy<X509SigningCredentials> SigningCredentials;
private readonly AppSettingsModel _appSettings;
private readonly IWebHostEnvironment HostingEnvironment;
private readonly ILogger<HomeController> _logger;
// Sample: Inject an instance of an AppSettingsModel class into the constructor of the consuming class,
// and let dependency injection handle the rest
public HomeController(ILogger<HomeController> logger, IOptions<AppSettingsModel> appSettings, IWebHostEnvironment hostingEnvironment)
{
_appSettings = appSettings.Value;
this.HostingEnvironment = hostingEnvironment;
this._logger = logger;
// Sample: Load the certificate with a private key (must be pfx file)
SigningCredentials = new Lazy<X509SigningCredentials>(() =>
{
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
"***************************************",
false);
// Get the first cert with the thumb-print
if (certCollection.Count > 0)
{
return new X509SigningCredentials(certCollection[0]);
}
throw new Exception("Certificate not found");
});
}
[HttpGet]
public ActionResult Index(string Name, string email, string phone)
{
if (string.IsNullOrEmpty(email))
{
ViewData["Message"] = "";
return View();
}
string token = BuildIdToken(Name, email, phone);
string link = BuildUrl(token);
return Redirect(link);
}
private string BuildIdToken(string Name, string email, string phone)
{
string issuer = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase.Value}/";
// All parameters send to Azure AD B2C needs to be sent as claims
IList<System.Security.Claims.Claim> claims = new List<System.Security.Claims.Claim>();
claims.Add(new System.Security.Claims.Claim("name", Name, System.Security.Claims.ClaimValueTypes.String, issuer));
claims.Add(new System.Security.Claims.Claim("email", email, System.Security.Claims.ClaimValueTypes.String, issuer));
if (!string.IsNullOrEmpty(phone))
{
claims.Add(new System.Security.Claims.Claim("phone", phone, System.Security.Claims.ClaimValueTypes.String, issuer));
}
// Create the token
JwtSecurityToken token = new JwtSecurityToken(
issuer,
"******************************************",
claims,
DateTime.Now,
DateTime.Now.AddDays(7),
HomeController.SigningCredentials.Value);
// Get the representation of the signed token
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
return jwtHandler.WriteToken(token);
}
private string BuildUrl(string token)
{
string nonce = Guid.NewGuid().ToString("n");
return string.Format("https://{0}.b2clogin.com/{0}.onmicrosoft.com/{1}/oauth2/v2.0/authorize?client_id={2}&nonce={4}&redirect_uri={3}&scope=openid&response_type=id_token",
"myTenant",
"B2C_1A_SIGNIN_WITH_EMAIL",
"************************************",
Uri.EscapeDataString("https://jwt.ms"),
nonce)
+ "&id_token_hint=" + token;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Location Location Location.
I was adjusting the base profile which I learned I should not be doing. When I applied my change to the extension file instead everything starting working properly.

FeignClient configuration in ASP.Net

I am trying to create microservices using Spring-boot Java and SteelToe ASP.NET
Step-1: I created a full service using Java (A service with UI and API. It is hosted on PCF). The API has ClassesControler defined inside.
Step-2: Create a microservice using ASP.NET, SteelToe. Register the service in Eureka and make it discoverable using Zuul.
Step-3: Use the Interface, Service approach to access the JAVA microservice(s)
namespace employee-client.Service
{
public interface IRelayService
{
Task<HttpResponseMessage> getClassesList(string relativeUrl = "/api/v1/classes");
}
}
Service with Implementation for Interface:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
DiscoveryHttpClientHandler _handler;
string _accessToken;
private const string BASE_URL = "https://www.example.com";
public QortaService(IDiscoveryClient client, string accessToken)
{
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList(string relativeUrl)
{
string classesUrl= BASE_URL + relativeUrl;
HttpClient client = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}
I came up with this approach based on the example in SteelToe but I hate hardcoding the BASE_URL.
Question: I very much like the #FeignClient annotation approach used in Java. Any ideas about how I can access an existing microservice in a better way. If so, an example would be much appreciated
Edit:
I modified the question to make more clear.
The flow of traffic is from Java Service to .NET service. .NET service requests for a list of classes from the controller in JAVA service (ClassesController.java)
I'm unclear which direction traffic is flowing in your scenario, but I think you're saying the .NET application is trying to call the Java application. The code you're using is from before HttpClientFactory was introduced and is a bit clunkier than what's possible now in general. Steeltoe can be used with HttpClientFactory for a better overall experience.
Steeltoe has debug logging available to confirm the results of service lookup if you set logging:loglevel:Steeltoe.Common.Discovery = true in your application config.
You didn't mention specifically what isn't working, but I'm guessing you're getting a 404 since it looks like your code will create a request path looking like https://fortuneService/api/fortunes/random/api/v1/classes
If you're looking for something like Feign in .NET, you could try out DHaven.Faux
For others who are looking for the same:
namespace employee-client.Service
{
public class RelayService : IRelayService
{
private const string CLASSES_API_SERVICEID = "classes-api";
IDiscoveryClient _discoveryClient;
DiscoveryHttpClientHandler _handler;
string _accessToken;
public RelayService(IDiscoveryClient discoveryClient, string accessToken)
{
_discoveryClient = discoveryClient;
_handler = new DiscoveryHttpClientHandler(client);
_accessToken = accessToken;
}
public async Task<HttpResponseMessage> getClassesList()
{
var classesApiInstances = _discoveryClient.GetInstances(CLASSES_API_SERVICEID);
Uri classesApiUri = classesApiInstances[0].Uri;
string classesUrl= classesApiUri.AbsoluteUri + relativeUrl;
HttpClient httpClient = GetClient();
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(classesUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead);
}
private HttpClient GetClient()
{
var client = new HttpClient(_handler, false);
return client;
}
}
}

AccessTokenProviderChain and OAuth2RestOperations

The Spring OAuth2 Developers Guide show the following under Persisting Tokens in a Client:
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations restTemplate() {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider()));
provider.setClientTokenServices(clientTokenServices());
return template;
}
However, I don't understand how the provider is part actually being used. Is this missing:
template.setAccessTokenProvider(provider);
Or is something else going on?
Yes, you are right, accessTokenProvider should be set in the template.
See https://www.baeldung.com/spring-security-oauth2-authentication-with-reddit

Adding AD authentication to OWIN Server Middleware pipeline

I have inherited a project that has been developed using OWIN, Server Middleware that manages a kind-of WebApi in order to communicate with mobile devices using ApiKeys. The Server side has a small web interface (which really is a set of test pages) but did not have authentication added. I am trying to wrap my head around the different frameworks being used and the ways one can authenticate around these OWIN techniques.
Let me show what I have first:
public class Startup
{
public void Configuration(IAppBuilder app)
{
log.Info("RT Server app starting up ...");
// Initialize the ApiKey Needed for ApiClient Library
ApiClient.ApiKey = Globals.ApiKey;
// Initialize the Services Library
Services.Startup.Initialize();//creates a configuration map of values for devices
// Setup Server Middleware
app.Use(typeof(ServerMiddleware), "RTrak.Server", "RTrak.Server");
app.Use(typeof(ServerMiddleware), "RTrak.Server.Pages", "RTrak.Server");
// HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];//throws an KeyNotFoundException
// listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
//ConfigureAuth(app)
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = MyAuthentication.ApplicationCookie,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider(),
CookieName = "RTrakCookie",
CookieHttpOnly = true,
ExpireTimeSpan = TimeSpan.FromHours(12), // ...
});
}
the ServerMiddleware
public ServerMiddleware(OwinMiddleware next, string baseNamespace, string defaultClass) : base(next)
{
BaseNamespace = baseNamespace;
DefaultClass = defaultClass;
}
public override async Task Invoke(IOwinContext owinContext)
{
var absolutePath = owinContext.Request.Uri.AbsolutePath;
string serverNamespace = BaseNamespace;
Type type;
string classToLoad = "";
if (absolutePath == "/")
classToLoad = DefaultClass;
else
{
classToLoad = absolutePath.Substring(1).Replace('/', '.');
if (classToLoad.EndsWith("."))
classToLoad = classToLoad.Substring(0, classToLoad.Length - 1);
}
type = Type.GetType($"{serverNamespace}.{classToLoad}, {serverNamespace}", false, true);
if (type == null)
{
classToLoad += ".Default";
type = Type.GetType($"{serverNamespace}.{classToLoad}, {serverNamespace}", false, true);
}
if (type != null)
{
try
{
object objService = Activator.CreateInstance(type);
((Resource)objService).Execute(owinContext);
}
catch (System.MissingMethodException)
{
//"403 INVALID URL");
}
}
else
await Next.Invoke(owinContext);
}
}
That ServerMiddleware is first calling Default Pages class that is HTML markup which links to the other test Pages
A thought was to add an MVC LoginController with AdAuthenticationService managing cookies Model to manage login that is configured as part of the Startup noted in the line ConfigAuth(app), but the middleware is ignoring the controller. Is MVC appropriate here?
then, I am looking at this ServerMiddleware and trying to understand how to intercept the Default page browser call with ActiveDirectory authentication.
I know that I may be overlooking something. Many thanks for anything (suggestions or resources) you can offer to help clear up this confusion for me.
What I did to resolve this was to leave the OWIN Middleware objects alone except for Startup.cs had to define CookieAuthentication route
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new Microsoft.Owin.PathString("/Auth/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
for the pages that were built as OWIN Resources. These OWIN Resource "Pages" then check
if (!this.Context.Authentication.User.Identity.IsAuthenticated)
I then implement an MVC controller that uses the AdAuthenticationService as above and UserManager to manage the AD credentials and Identity where the OWIN resource redirects to the MVC view+controller for authentication. That controller handles the login page actions. Upon authentication, the MVC redirects to the OWIN resource pages.
Thus, OWIN Middleware and MVC can live side-by-side so long as OWIN does not try to define the routes that MVC wants to use. Owin can maintain its own authentication as well.

Disable GET functionality of WEB API token call

I developed an application using ASP.NET WEB API 2. The application is completed and in the process of having security review done on it, but one of the requirements is that any GET requests for login must be disabled.
We are making the call to the token action over POST, but the security team picked up that you can still make the same request with GET and that needs to be removed. I know the token call is one that is built into the whole OWIN/OAUTH system, but is it possible to configure it so that it will only accept POST requests and block GET?
Thanks in advance.
By looking into Katana project sources I can see that in Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerHandler they have the following check:
if (Options.TokenEndpointPath.HasValue && Options.TokenEndpointPath == Request.Path)
{
matchRequestContext.MatchesTokenEndpoint();
}
As you can see there is no additional check for HTTP METHOD. Therefore as one of the possible solution I can propose you to write your own middleware which is executing before authentication one and checks for the HTTP METHOD:
public class OnlyPostTokenMiddleware : OwinMiddleware
{
private readonly OAuthAuthorizationServerOptions opts;
public OnlyPostTokenMiddleware(OwinMiddleware next, OAuthAuthorizationServerOptions opts) : base(next)
{
this.opts = opts;
}
public override Task Invoke(IOwinContext context)
{
if (opts.TokenEndpointPath.HasValue && opts.TokenEndpointPath == context.Request.Path && context.Request.Method == "POST")
{
return Next.Invoke(context);
}
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.ReasonPhrase = "Not Found";
return context.Response.WriteAsync("Not Found");
}
}
then in Startup.cs you would have something similar to:
var authOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = Resolver.GetService<OAuthProvider>(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1)
};
app.Use<OnlyPostTokenMiddleware>(authOptions);
app.UseOAuthAuthorizationServer(authOptions);

Resources