GAE Open-ID Federated Authentication using Java Script End Point - google-app-engine

I want to use GAE Open-ID Federated Authentication using Java Script (without Java Servlet) End Point.
Is it supported via Java Script (without Java Servlet) End Point?
If yes how could I use Open-ID Federated Authentication with Java Script end point API call?
I had tried User in endpoint API
#ApiMethod(
name = "signMe.signGoogleId",
httpMethod = "POST",
scopes = { "https://www.googleapis.com/auth/userinfo.profile" , "https://www.googleapis.com/auth/userinfo.email" }
)
public SignIn signInOpenId(User user)throws
OAuthRequestException, IOException
{
User is null always even after logging with Google\Yahoo User.
I had tried HttpServletRequest req in endpoint API.
#ApiMethod(
name = "SignMe.signOpenId",
httpMethod = HttpMethod.GET,
scopes = { "https://www.googleapis.com/auth/userinfo.profile" , "https://www.googleapis.com/auth/userinfo.email" }
)
public SignIn signInOpenId(HttpServletRequest req)
throws IOException
{
UserService userService = UserServiceFactory.getUserService();
User newUser = userService.getCurrentUser();
newUser is null always even after logging with Google\Yahoo User.
Thanks,
Deepak

You could always dump the cloud endpoints way of doing it and go with general API authentication.
https://developers.google.com/appengine/articles/openid#fa
As would be done for non Cloud Endpoints applications. The down side is you wouldn't get any client authorization abilities, so any client code hit your API.
Google Cloud Endpoints OAuth does not seem to support Open ID at this time.

Related

ASP.NET 6 WebAPI Authentication with SSO

I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.

Azure Authentication from client app to another API

I'm trying to get azure AD authentication working between a Blazor WASM app, and another API that I have running locally but on a different port. I need both applications to use the Azure login, but I only want the user to have to log in once on the Blazor app which should then pass those credentials through to the API.
I've set up app registrations for both apps in the portal, created the redirect url, exposed the API with a scope and I can successfully log into the blazor app and see my name using #context.User.Identity.Name.
When it then tries to call the API though, I get a 401 error back and it doesn't hit any breakpoints in the API (presumably because there is no authentication being passed across in the http request).
My code in the Blazor app sets up a http client with the base address set to the API:
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("APIClient", client => client.BaseAddress = new Uri("https://localhost:11001"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("APIClient"));
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://d3152e51-9f5e-4ff7-85f2-8df5df5e2b2e/MyAPI");
//options.UserOptions.RoleClaim = "appRole";
});
await builder.Build().RunAsync();
}
In my API, I just have the Authorise attribute set on the class, and eventually will need roles in there too:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class CarController
Then, in my Blazor component, I then inject the http factory and try to make a request:
#inject IHttpClientFactory _factory
...
private async Task RetrieveCars()
{
var httpClient = _factory.CreateClient("APIClient");
HttpResponseMessage response = await httpClient.GetAsync("https://localhost:11001/api/cars");
var resp = await response.Content.ReadAsStringAsync();
cars = JsonSerializer.Deserialize<List<Car>>(resp);
}
but this returns the 401 error. I've also tried a few different variations like just injecting a http client (#inject HttpClient Http) but nothing seems to be adding my authorisation into the API calls. The options.UserOptions.RoleClaim is also commented out in the AddMsalAuthentication section as I wasn't sure if it was needed, but it doesn't work with or without it in there.
Can anyone explain what I'm doing wrong and what code I should be using?
Common causes.
Most cases ,we tend to forget to grant consent after giving API
permissions in the app registration portal,after exposing the api
which may lead to unauthorized error.
Other thing is when Audience doesn’t match the “aud” claim when we
track the token in jwt.io .Make sure ,Audience=clientId is configured
in the code in authentication scheme or Token validation parameters
by giving ValidAudiences.And also try with and without api:// prefix
in client id parameter.
Sometimes aud claim doesn’t match as we mistakenly send ID token
instead of Access tokens as access tokens are meant to call APIs .So
make sure you check mark both ID Token and access token in portal
while app registration.
While Enabling the authentication by injecting the [Authorize]
attribute to the Razor pages.Also add reference
Microsoft.AspNetCore.Authorization as(#using
Microsoft.AspNetCore.Authorization)
Please see the note in MS docs and some common-errors
If above are not the cases, please provide with additional error details and startup configurations or any link that you are following to investigate further.

How to use Spring Boot CAS authentication with React frontend

My application has an API server (Spring Boot) and a client-facing server (React), the React server will make API requests on behalf of an authenticated user. For authentication, I use a CAS server provided by a third-party.
I used the Java Apereo CAS Client (version 3.6.2), and add the following API endpoints for the React app to call:
#RestController
public class CASController {
#Value("${cas.server-url-prefix}")
private String CASUrl;
#GetMapping("/login")
public String getCASUserId(HttpServletRequest request){
return request.getRemoteUser();
}
#GetMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession(false);
if(session != null || !request.isRequestedSessionIdValid()) {
String id = session.getId();
session.invalidate();
log.info("JSESSIONID: " + id + " is valid: " + request.isRequestedSessionIdValid());
}
try{
response.sendRedirect(CASUrl + "/logout");
}
catch (IOException | IllegalStateException e){
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
}
}
}
The above endpoints work if the Spring Boot app interacts with the client directly but it doesn't, it accepts requests from the React server. When the React app tries to call: localhost:8080/login, it failed because of the redirection.
A way to work around this is to call the CAS server from the React app, then send the required info (service URL & ticket issued by the CAS server) to the backend, and let the backend make the ticket validation call, if the ticket validation success, then the user is authenticated and a session id is sent back to the React app for subsequent requests.
My questions are:
Does the workaround even make sense? Authentification is split between the frontend (get a ticket from CAS server) and the backend (validate the issued ticket with CAS server and issue session). If it doesn't, what is a better solution?
For the workaround, I think I will need to use Spring Security for this instead of Java Apereo CAS Client? If that's the case, how do I do it?
I tried something like this:
Ticket validation endpoint:
#GetMapping("/login")
public String getCASUserId(#RequestParam #NotBlank String URL,
#RequestParam #NotBlank String ticket,
HttpServletRequest request){
// make the call the the CAS server for ticket validation
String userId = userService.getUserId(url, ticket);
HttpSession session = request.getSession();
session.setAttribute("userId", userId);
return userId;
}
The ticket is validated, session issued but no endpoint is actually protected since neither spring-security nor Java Apereo CAS Client is enabled.

GCP Server to Server Authentication with Service Account

I'm trying to authenticate a request from my Google Cloud Function to my API on App Engine (Standard environment).
I have something working, but I'm new to OAuth2 and am looking for a sanity check.
In my Cloud Function, I send an authenticated request to my API doing the following:
import { GoogleAuth } from 'google-auth-library';
// Send Request Code:
const auth = new GoogleAuth();
const tokenClient = await auth.getIdTokenClient(`/protectedEndpoint`);
await tokenClient.request({
url: `https://${process.env.GCLOUD_PROJECT}.appspot.com/protectedEndpoint`,
method: 'POST',
});
In the API (on App Engine), I do the following:
import { GoogleAuth } from 'google-auth-library';
// Handle Request Code:
const token = <Bearer token parsed from request headers>
const googleAuth = new GoogleAuth();
const tokenClient = await googleAuth.getIdTokenClient('');
const loginTicket = await tokenClient.verifyIdToken({
idToken: token,
audience: '/protectedEndpoint',
});
if (loginTicket.getUserId() !== process.env.SERVICE_ACCOUNT_ID)) {
throw new Error('Unauthenticated Service Account');
}
return 'Successful Authentication'
Note: In both cases, I'm using Google's default application credentials to initialize the GoogleAuth client. (my Default App Engine service account)
This all works. My function sends a request to my API, and my API is able to parse the bearer token and tell me that it came from my approved service account... but I'm not 100% confident that this is actually secure. Is it possible for someone to spoof my service account without having its credentials?
Thanks in advance!
Is it possible for someone to spoof my service account without having
its credentials?
A precise answer requires the specification of time. Given enough time and processing power, any authentication/authorization/encryption/hashing/signing method can be broken.
A Google service account contains an RSA 2048-bit private key. Current guesstimates are 300 trillion years to break RSA 2048 bit encryption. With the rapid advances in computers, let's assume your data will probably not be of any use/value by the time RSA is broken.
The private key is used to sign a JWT. The Signed JWT is used to request OAuth Access/Identity Tokens.
Spoofing would require signing with the same private key. Therefore, spoofing is not possible with today's technology.
Stealing/leaking the private key or the generated OAuth tokens is the only realistic method today.

AppEngine Cloud Endpoints and custom Users service

Is it possible to use a custom Users service (a service that implements UsersService interface) with Google AppEngine endpoints?
Let's take the example from Google AppEngine website
#ApiMethod(name = "greetings.authed", path = "greeting/authed")
public HelloGreeting authedGreeting(User user) {
HelloGreeting response = new HelloGreeting("hello " + user.getEmail());
return response;
}
When a request is made to the application and this endpoint is hit it somehow contacts the Users service and gets the information about authenticated user (because the OAuth token is available in the request).
I'd like to somehow use my own service which would implement UsersService interface and provide me with a user object.

Resources