In the new Spring Boot 3.0.1 I'm adding support for CSRF protection by adding this simple configuration as per Spring documentation:
http.csrf().csrfTokenRepository(tokenRepository);
and
#Bean
public CsrfTokenRepository tokenRepository() {
var tokenRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepo.setCookiePath("/");
return tokenRepo;
}
On the front-end side, there is an Angular app with import of the standard library
import {HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule} from '#angular/common/http';
However I cannot make it work with the standard workflow, like
Execute request to get XCSRF-TOKEN cookie from the server.
Extract the token value from the cookie.
Add X-XSRF-TOKEN header with the extract value from the cookie.
The front-end sends both cookie and header with the same token value and it fails in check:
public final class XorCsrfTokenRequestAttributeHandler ...
private static String getTokenValue(String actualToken, String token) {
byte[] actualBytes;
try {
actualBytes = Base64.getUrlDecoder().decode(actualToken);
}
catch (Exception ex) {
return null;
}
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
return null;
}
The methods always returns null for
if (actualBytes.length < tokenSize) {
return null;
}
The only way to make it work is to return encoded string to be pasted in the header directly by adding endpoint, like this:
DeferredCsrfToken deferredCsrfToken = repository.loadDeferredToken(request, response);
requestHandler.handle(request, response, deferredCsrfToken::get);
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
return csrfToken.getToken();
So the returned value to be used in header looks like:
ab5tfqabXomGPuLjDQk96mVMZHNOh_JnpPVM4F_hQU1sOMyWW9sLSsCtb72rWNPabyQJ01EpSUsvtctKwcQphWeAJX9ZDPmg
instead of
5adaf830-40e6-43c9-b42a-e36fd713c1a6
Any advice on what I'm missing here?
I am currently working on a Spring Boot REST API. I have successfully added login using client credentials with Spring Oauth and Spring Security (I can successfully get access token and refresh token using /oauth/token endpoint).
But now I want to provide social login with Facebook and Google. As I understand, this is the flow.
User clicks Login with Social button in React frontend.
Then, he will be asked to grant access. (Still in React)
After that he will be redirected to the react front end with an access token.
Frontend sends that access token to the Spring Boot backend. (I don't know to what endpoint)
Then backend uses that access token to fetch details from the Facebook/Google and check whether a such user exists in our database.
If such user exists, backend will return access and refresh tokens to the frontend.
Now frontend can consume all the endpoints.
My problem is, I have no idea about the steps 4,5 and 6.
Do I have to make a custom endpoint to receive FB/Google access tokens?
How do I issue custom access and refresh tokens in Spring Boot?
I would really appreciate it if you could help me with this scenario.
The flow it's the following:
Front-End calls spring to /oauth2/authorization/facebook(or whatever client do you wanna use)
Back-end respond with a redirect to Facebook login page(including in the query params, client_id, scope, redirect_uri(must be the same present on your developer console) and state which is used to avoid XSRF attacks, according to OAuth2 Standards)
you can see more details here https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
3) Once the user log-in and accept whatever popup facebook or other services will show, the user will be redirected to the page present in "redirect_uri", this page should be a component of your ReactJs. The callback will come with some datas put in the query params, usually those params are two, state(it's the same you sent to facebook) and code(which is used from the BE to end the login flow).
Once facebook or whatever service, called you back, you have to take those 2 params, from the url(using JS for instance) and call the /login/oauth2/code/facebook/?code=CODE_GENERATED_BY_FACEBOOK&?state=STATE_GENERATED_BY_SPRING
Spring will call the facebook service(with an implementation of OAuth2AccessTokenResponseClient, using your secret_token, client_id, code and few other fields. Once facebook responds with the access_token and refresh_token, spring call an implementation of OAuth2UserService, used to get user info from facebook using the access_token created a moment before, at facebook's response a session will be created including the principal. (You can intercept the login success creating an implementation of SimpleUrlAuthenticationSuccessHandlerand adding it to your spring security configuration. (For facebook, google and otka in theory OAuth2AccessTokenResponseClient and OAuth2UserService implementations should already exist.
In that handler you can put the logic to add and look for an existing user.
coming back to the default behavior
Once spring created the new session and gave you the JSESSIONID cookie, it will redirect you to the root (I believe, I don't remember exactly which is the default path after the login, but you can change it, creating your own implementation of the handler I told you before)
Note: access_token and refresh_token will be stored in a OAuth2AuthorizedClient, stored in the ClientRegistrationRepository.
This is the end. From now then you can call your back end with that cookie and the be will see you as a logged user. My suggestion is once you got the simple flow working, you should implement a JWT token to use and store in the localstorage of your browser instead of using the cookie.
Hopefully I gave you the infos you were looking for, if I missed something, misunderstood something or something it's not clear let me know in the comment.
UPDATE (some java samples)
My OAuth2 SecurityConfig :
NOTE:
PROTECTED_URLS it's just : public static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
PUBLIC_URLS it's just: private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( new AntPathRequestMatcher("/api/v1/login"));
Also note I'm using a dual HttpSecurity configuration. (But in this case it's useless to public that too)
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class OAuth2ClientSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JWTService jwtService;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
private final OAuth2AuthorizedClientService clientService;
private final UserAuthenticationService authenticationService;
private final SimpleUrlAuthenticationSuccessHandler successHandler; //This is the default one, this bean has been created in another HttpSecurity Configuration file.
private final OAuth2TokenAuthenticationProvider authenticationProvider2;
private final CustomOAuth2AuthorizedClientServiceImpl customOAuth2AuthorizedClientService;
private final TwitchOAuth2UrlAuthSuccessHandler oauth2Filter; //This is the success handler customized.
//In this bean i set the default successHandler and the current AuthManager.
#Bean("oauth2TokenAuthenticaitonFilter")
TokenAuthenticationFilter oatuh2TokenAuthenticationFilter() throws Exception {
TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#PostConstruct
public void setFilterSettings() {
oauth2Filter.setRedirectStrategy(new NoRedirectStrategy());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider2);
}
#Bean
public RestOperations restOperations() {
return new RestTemplate();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/twitch/**").authenticated()
.and().csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable().authenticationProvider(authenticationProvider2)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.addFilterBefore(oatuh2TokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.oauth2Login().successHandler(oauth2Filter).tokenEndpoint()
.accessTokenResponseClient(new RestOAuth2AccessTokenResponseClient(restOperations()))
.and().authorizedClientService(customOAuth2AuthorizedClientService)
.userInfoEndpoint().userService(new RestOAuth2UserService(restOperations(), tempUserDataService, authorizedClientRepo));
}
#Bean
FilterRegistrationBean disableAutoRegistrationOAuth2Filter() throws Exception {
FilterRegistrationBean registration = new FilterRegistrationBean(oatuh2TokenAuthenticationFilter());
registration.setEnabled(false);
return registration;
}
}
By the fact that my SessionCreationPolicy.STATELESS the cookie created by spring after the end of the OAuth2 Flow is useless. So once the process its over I give to the user a TemporaryJWT Token used to access the only possible service (the register service)
My TokenAutheticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String AUTHORIZATION = "Authorization";
private static final String BEARER = "Bearer";
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION))
.map(v -> v.replace(BEARER, "").trim())
.orElseThrow(() -> new BadCredentialsException("Missing authentication token."));
Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(401);
}
}
TwitchOAuth2UrlAuthSuccessHandler (This is where all the magic happens):
This handler is called once the userService and the userService is called when the user calls api.myweb.com/login/oauth2/code/facebook/?code=XXX&state=XXX. (please don't forget the state)
#Component
#RequiredArgsConstructor
public class TwitchOAuth2UrlAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final OAuth2AuthorizedClientRepo oAuth2AuthorizedClientRepo;
private final UserAuthenticationService authenticationService;
private final JWTService jwtService;
private final Gson gson;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
response.setStatus(200);
Cookie cookie = new Cookie("JSESSIONID", null);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
Optional<OAuth2AuthorizedClientEntity> oAuth2AuthorizedClient = oAuth2AuthorizedClientRepo.findById(new OAuth2AuthorizedClientId(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
if (oAuth2AuthorizedClient.isPresent() && oAuth2AuthorizedClient.get().getUserDetails() != null) {
response.getWriter().write(gson.toJson(authenticationService.loginWithCryptedPassword(oAuth2AuthorizedClient.get().getUserDetails().getUsername(), oAuth2AuthorizedClient.get().getUserDetails().getPassword())));
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().flush();
} else {
response.setHeader("Authorization", jwtService.createTempToken(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
}
}
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
return "";
}
}
RestOAuth2AccessTokenResponseClient (its responsable to take Access_token and refresh_token from FB)
public class RestOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
private final RestOperations restOperations;
public RestOAuth2AccessTokenResponseClient(RestOperations restOperations) {
this.restOperations = restOperations;
}
#Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
MultiValueMap<String, String> tokenRequest = new LinkedMultiValueMap<>();
tokenRequest.add("client_id", clientRegistration.getClientId());
tokenRequest.add("client_secret", clientRegistration.getClientSecret());
tokenRequest.add("grant_type", clientRegistration.getAuthorizationGrantType().getValue());
tokenRequest.add("code", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
tokenRequest.add("redirect_uri", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
tokenRequest.add("scope", String.join(" ", authorizationGrantRequest.getClientRegistration().getScopes()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
ResponseEntity<AccessResponse> responseEntity = restOperations.exchange(tokenUri, HttpMethod.POST, new HttpEntity<>(tokenRequest, headers), AccessResponse.class);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
AccessResponse accessResponse = responseEntity.getBody();
Set<String> scopes = accessResponse.getScopes().isEmpty() ?
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes() : accessResponse.getScopes();
return OAuth2AccessTokenResponse.withToken(accessResponse.getAccessToken())
.tokenType(accessResponse.getTokenType())
.expiresIn(accessResponse.getExpiresIn())
.refreshToken(accessResponse.getRefreshToken())
.scopes(scopes)
.build();
}
}
UserService
public class RestOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final RestOperations restOperations;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
public RestOAuth2UserService(RestOperations restOperations, TempUserDataService tempUserDataService, OAuth2AuthorizedClientRepo authorizedClientRepo) {
this.restOperations = restOperations;
this.tempUserDataService = tempUserDataService;
this.authorizedClientRepo = authorizedClientRepo;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
String userInfoUrl = oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", oAuth2UserRequest.getAccessToken().getTokenValue()));
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
if (oAuth2UserRequest.getClientRegistration().getClientName().equals("OAuth2 Twitch")) {
headers.add("client-id", oAuth2UserRequest.getClientRegistration().getClientId());
}
ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {
};
ResponseEntity<Map<String, Object>> responseEntity = restOperations.exchange(userInfoUrl, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
Map<String, Object> userAttributes = responseEntity.getBody();
userAttributes = LinkedHashMap.class.cast(((ArrayList) userAttributes.get("data")).get(0));
OAuth2AuthorizedClientId clientId = new OAuth2AuthorizedClientId(oAuth2UserRequest.getClientRegistration().getRegistrationId(), String.valueOf(userAttributes.get("id")));
Optional<OAuth2AuthorizedClientEntity> clientEntity = this.authorizedClientRepo.findById(clientId);
if (!clientEntity.isPresent() || clientEntity.get().getUserDetails() == null) {
TempUserData tempUserData = new TempUserData();
tempUserData.setClientId(clientId);
tempUserData.setUsername(String.valueOf(userAttributes.get("login")));
tempUserData.setEmail(String.valueOf(userAttributes.get("email")));
tempUserDataService.save(tempUserData);
}
Set<GrantedAuthority> authorities = Collections.singleton(new OAuth2UserAuthority(userAttributes));
return new DefaultOAuth2User(authorities, userAttributes, oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
}
As asked this is all the code you need, just to give you another hint. When you call /login/oauth2/code/facebook/?code=XXX&?state=XXX the chain is the following:
RestOAuth2AccessTokenResponseClient
RestOAuth2UserService
TwitchOAuth2UrlAuthSuccessHandler
I hope this can help you. Let me know if you need more explainations.
My setup:
Java backend hosted on Google App Engine containing APIs that were created using Google Cloud Endpoints
Mobile client applications containing generated client libraries for the endpoints mentioned above. Also integrated with Firebase for authentication and the database.
My intention is that a user of the mobile client applications will be able to log in to the mobile app using Firebase authentication, then connect to any of the backend APIs, which in turn will do some processing and then read or write data to/from the Firebase database.
To secure the APIs on the server, I think I'll have to use the built-in verifyIdToken() method of the Firebase Server SDK to (see Verifying ID Tokens on Firebase) to decode a user's ID token passed from the client application. As verifyIdToken() runs asynchronously, how would I integrate it with an API method in GAE? I have something similar to the following so far:
#ApiMethod(name = "processAndSaveToDB", httpMethod = "post")
public Response processAndSaveToDB(#Named("token") String token) {
Response response = new Response();
// Check if the user is authenticated first
FirebaseAuth.getInstance().verifyIdToken(idToken)
.addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
// do bulk of processAndSaveToDB() method
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(Exception e) {
// throw unauthorized exception
});
return response;
}
As this authentication task is running asynchronously in task queue, you can wait until that task is ended and continue in synchronous way, optionally you can add listeners onSuccess, onFailure and onComplete.
Task<FirebaseToken> authTask = FirebaseAuth.getInstance().verifyIdToken(idToken)
.addOnSuccessListener(new OnSuccessListener() {
#Override
public void onSuccess(Object tr) {//do smtg }
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(Exception excptn) {//do smtg }
}).addOnCompleteListener(new OnCompleteListener() {
#Override
public void onComplete(Task task) {//do smtg }
});
try {
Tasks.await(authTask);
} catch(ExecutionException | InterruptedException e ){
//handle error
}
FirebaseToken decodedToken = authTask.getResult();
I have an SPA app built with AngularJS, the backend is WebApi2. I´m struggling with Authentication and Authorization. What I want in the long run is to enable authentication against Active Directory. But for now, I just trying to enable authorization for my APiControllers and setting a Cookie with Owin.
Here is my Owin Identity Helper class, I´m only adding 1 claim that is the serialized user info:
public void SignIn(bool rememberMe, T user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.UserData, JsonConvert.SerializeObject(user)),
};
var claimsIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = rememberMe }, claimsIdentity);
}
Here is authentication in controller:
[HttpGet, HttpPost]
[AllowAnonymous]
[Route("authenticate")]
public HttpResponseMessage Authenticate()
{
var authenticated = IdentityContext.Current.IsAuthenticated;
if (!authenticated)
{
var user = new User();
user.Email = "roger#moore.com";
user.Name = "Roger Moore";
user.Id = 23;
IdentityContext.Current.SignIn(true, user);
return new HttpResponseMessage()
{
Content = new StringContent(
JsonConvert.SerializeObject(user),
Encoding.UTF8,
"application/json"
)
};
}
else
{
//return the user if authenticated
return new HttpResponseMessage()
{
Content = new StringContent(
JsonConvert.SerializeObject(IdentityContext.Current.User), //from claim
Encoding.UTF8,
"application/json"
)
};
}
}
My StartUp class
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/account/signedout")
});
}
}
When I call the authenticate user I´m setting signing in, but when calling a controller with [Authorize] attribute, im not signed in. Furthermore, when having fiddler running I get the error:
"[Fiddler] Response Header parsing failed. This can be caused by an illegal HTTP response earlier on this reused server socket-- for instance, a HTTP/304 response which illegally contains a body. Response Data:"
Does anyone have any suggestions, or alternatives with example code for using JWT Token Authentication and Authorization From Angular to WebApi2?
I followed the instructions in this tutorial.
https://developers.google.com/appengine/docs/java/endpoints/getstarted/auth
when i deployed my code. and went to test my app.
with the following url
http://chandru-compute.appspot.com/_ah/api/explorer
My
helloworld.greetings.multiply and
helloworld.greetings.getGreeting works as expected.
But i have issues with the helloworld.greetings.authed method.
The user object is always null.
Here is the code.
package com.google.devrel.samples.helloendpoints;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import javax.inject.Named;
import java.util.ArrayList;
/**
* Defines v1 of a helloworld API, which provides simple "greeting" methods.
*/
#Api(
name = "helloworld",
version = "v1",
clientIds = {com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID}
)
public class Greetings {
public static ArrayList<Greeting> greetings = new ArrayList<Greeting>();
static {
greetings.add(new Greeting("hello world!"));
greetings.add(new Greeting("goodbye world!"));
}
public Greeting getGreeting(#Named("id") Integer id) {
return greetings.get(id);
}
#ApiMethod(name = "greetings.multiply", httpMethod = "post")
public Greeting insertGreeting(#Named("times") Integer times, Greeting greeting) {
Greeting response = new Greeting();
StringBuilder responseBuilder = new StringBuilder();
for (int i = 0; i < times; i++) {
responseBuilder.append(greeting.getMessage());
}
response.setMessage(responseBuilder.toString());
return response;
}
#ApiMethod(name = "greetings.authed", path = "greeting/authed")
public Greeting authedGreeting(User user) {
//Greeting response = new Greeting("hello " + user.getEmail());
Greeting response;
if (user == null) {
UserService userService = UserServiceFactory.getUserService();
User user2 = userService.getCurrentUser();
String text = null;
if (user2 != null){
text = user2.getEmail();
}
response = new Greeting("hello world : Email2" + text );
} else {
response = new Greeting("hello world : Email " + user.getEmail() );
}
return response;
}
}
I had same problem, it helped for me to add
scopes = {"https://www.googleapis.com/auth/userinfo.email"}
into my Greetings #Api annotation. So the whole final #Apilook like
#Api(
name = "helloworld",
version = "v1",
clientIds = { com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID },
scopes = {"https://www.googleapis.com/auth/userinfo.email"}
)
Then deploy, reload Api Explorer page and also turn on "Authorize requests using OAuth 2.0" option with same scope.
I am getting the same problem. And if you throw an OAuthRequestException Exception and test the service via the API Explorer console, you will get a message saying This method requires you to be authenticated. You may need to activate the toggle above to authorize your request using OAuth 2.0. When you try to enable the OAuth 2.0 toggle it requests in a new window to Select OAuth 2.0 scopes, and I have not been able to find which scopes are needed or figure out how I can test a cloud end-point service with authorization from the API Explorer console.
First of all, in the API explorer, you need to authenticate the request with OAuth using the Authorize requests using OAuth 2.0 toggle in the user interface.
If the user is still null check that among the client ids there is the ID for the API explorer
#Api(
name = "myAPIName",
version = "v1",
clientIds = { com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID }
)
This is the only thing that is needed to obtain a not null User argument.