How to call custom endpoints with token on SAP Spartacus storefront? - spartacus-storefront

I'm new on SAP Spartacus storefront, and I'm trying to make a custom API call to check if CNPJ is valid on customer database.
My backend is ready and needs to receive a Token on request header.
But I don't find on Spartacus documentation how to use a default token service or interceptor to make this call.
Anyone can help me?
Thanks for your time, have a good day.

You can take a look at the Spartacus Session Management documentation.
If you need to send the user token, by default it is included in the
header of every request made to "occ". This means that if you want to
have this same behavior for an endpoint not in OCC you will need to
extend and overwrite AuthHttpHeaderService more specifically the
isOccUrl method so that it returns "true" for occ endpoints and
your external endpoints. It will look like this:
[...]
#Injectable({
providedIn: 'root',
})
export class CustomAuthHttpHeaderService extends AuthHttpHeaderService {
constructor(
protected authService: AuthService,
protected authStorageService: AuthStorageService,
protected oAuthLibWrapperService: OAuthLibWrapperService,
protected routingService: RoutingService,
protected occEndpoints: OccEndpointsService,
protected globalMessageService: GlobalMessageService
) {
super(...);
}
protected isOccUrl(url: string): boolean {
return url.includes(this.occEndpoints.getBaseUrl()) || url === 'my-custom-url';
}
}
And for the injection:
{
provide: AuthHttpHeaderService,
useClass: CustomAuthHttpHeaderService
}
If you need the client token you simply need to place the following
string 'cx-use-client-token' in your request's header. If it's
there and the user is anonymous, the ClientTokenInterceptor will
add the client token. It should be used as follow:
[...]
let headers = new HttpHeaders({
'Content-Type': 'application/json',
});
headers = InterceptorUtil.createHeader(USE_CLIENT_TOKEN, true, headers);
// Your request
return this.http.post('/your-request-endpoints', { headers });

Related

How to secure a REST API with an API key

I'm currently creating a Rest API with NestJS (it's really cool by the way).
In this API, I'm using JWT (Json Web Token) to allow users to log in and view different resources based on their role.
However, I want to implement an API Key system to protect the API itself. I don't want any developer to be able to use my api. I want him to go through this API Key to use my api.
Either by a query in the url : https://domaine.com?api_key=${API_KEY} or via the header :
GET /v1/some-resource
Host: docmaine.com
Accept: application/json
X-API-KEY: MyAwes0m3API_KeY
Do you have a tutorial, course or a track to advise me?
Why not create a guard that checks for the validity of that header?
#Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly apiKeyService: ApiKeyService) {} // made up service for the point of the exmaple
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const key = req.headers['X-API-KEY'] ?? req.query.api_key; // checks the header, moves to query if null
return this.apiKeyService.isKeyValid(key);
}
}
And now you can use #UseGuards(ApiKeyGuard) on whatever route, or bind it globally, and you've got some basic authentication up for your server.

Response to preflight request doesn't pass access control check: It does not have HTTP ok status. GET working POST PUT DELETE not working

Greetings
I have one web application with following architecture:
Web api: ASP.net core 2.1 (Windows Authentication)
UI: angular 8
UI is able to get data but unable to send data.
I mean GET method is working fine but POST, PUT, DELETE options are not working .
And all the methods are working using POSTMAN.
ERROR is:
Access to XMLHttpRequest at 'http://xx.xxx.xxx.xx:xxyy/xxx/xxxxxx/Method' from origin 'http://localhost:xxxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Any help will be appreciated .
Thanks in advance :)
That's because your API is on different domain than your SPA angular application.
Please at this at the start of your Configure method in Startup.cs
if (env.IsDevelopment())
{
app.UseCors(opts =>
{
opts.WithOrigins(new string[]
{
"http://localhost:3000",
"http://localhost:3001"
// whatever domain/port u are using
});
opts.AllowAnyHeader();
opts.AllowAnyMethod();
opts.AllowCredentials();
});
}
Please note that this will handle only CORS for local development since you'll probably have same domain in production - if not, you'll need to reconfigure this for production also.
CORS blocking is browser specific and that's why it's working in PostMan but not in browser.
This is what i use and it should work i hope for your case.
My startup.cs ConfigureServices() decorated with:
services.AddCors(feature =>
feature.AddPolicy(
"CorsPolicy",
apiPolicy => apiPolicy
//.AllowAnyOrigin()
//.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed(host => true)
.AllowCredentials()
));
And, Configure() method with:
app.UseCors("CorsPolicy");
Notice the SetIsOriginAllowed() and allowCreds() along with other policy settings, this works for me with POST calls to my api from my angular, which are running on two different port#s.
UPDATE:
Following the questions on the comments, adding additional information on how do we check the logged in user (windows auth) btwn api and the angular (frontend).
You can check the incoming User on a specific route that would only expect the authenticated user using the decoration [Authorize]. In my case, i would have only one method that would expect the windows user in the api:
[HttpGet("UserInfo")]
[Authorize]
public IActionResult GetUserInfo()
{
string defaultCxtUser = HttpContext?.User?.Identity?.Name;
if (defaultCxtUser != null && !string.IsNullOrEmpty(defaultCxtUser))
{
_logger.LogDebug($"START - Get Context user details for {defaultCxtUser}");
ADHelper.logger = _logger;
var userFullName = ADHelper.GetUserIdentityInfo(defaultCxtUser);
_logger.LogInformation($"Context user {defaultCxtUser} with name: {userFullName}");
var userInfo = new { Name = userFullName };
//_logger.LogDebug($"END - GetUserInfo({defaultCxtUser} for {userFullName}");
return Ok(userInfo);
}
else
return Ok(new { Name = defaultCxtUser });
}
then i would call this from my angular with the service call as,
// Get the Logged in user info
GetCurrentUserInfo(): Observable<string> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true
};
// return this.http.get<string>(`${ApiPath}UserInfo`, httpOptions)
// .pipe(map(v => v as string));
return this.http.get<UserInfo>(`${ApiPath}UserInfo`, httpOptions)
.pipe(map(data => {
// console.log(data, data.Name);
return data.Name;
}))
;
}
Please see the headers with 'withCredentials: true' line that would trigger to pass the current user info, and it would be read and understood only if it has the authorize attr to read the User.Identity object in c# side. The reason we do this on a specific method is that, there should be some other parental method in the api like ApiStatus() or anything that could be, should be called first. This would ensure to also invoke the preflight check with OPTIONS that would require anonymous auth. Like in my case, getting whether the api is available and running, and some other app environment info before i get the userInfo() from my angular app.

Is is possible to disable authentication providers for specific routes?

We're evaluating service stack v.4.5.6.0 for a Web API and we want clients to be able to authenticate using basic auth or credentials but we do not want them to be able to provide a basic auth header in place of a JWT token or session cookie when using our services. While I realize this is somewhat arbitrary, is there a way to exclude routes from specific providers or force the use of a token/cookie to authenticate once they've logged in?
Auth config from AppHost:
private void ConfigureAuth(Container container)
{
var appSettings = new AppSettings();
this.Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[]
{
new CredentialsAuthProvider(),
new BasicAuthProvider(),
new JwtAuthProvider(appSettings)
}) { IncludeAssignRoleServices = false, MaxLoginAttempts = 10} );
var userRepository = new CustomUserAuthRepository(container.TryResolve<IDbConnectionFactory>());
container.Register<IAuthRepository>(userRepository);
}
ServiceStack lets you decide which AuthProviders you want your Services to be authenticated with, but it doesn't let you individually configure which adhoc AuthProviders applies to individual Services. Feel free to add this a feature request.
However if you want to ensure that a Service is only accessed via JWT you can add a check in your Services for FromToken which indicates the Session was populated by a JWT Token, e.g:
[Authenticate]
public class MyServices : Service
{
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (!session.FromToken)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
}
From v4.5.7 that's now available on MyGet you can also use the new session.AuthProvider property which indicates what AuthProvider was used to Authenticate the user, e.g:
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (session.AuthProvider != JwtAuthProvider.Name)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
Refer to the docs for different AuthProvider names for each AuthProvider.

Alternative to cookie based session/authentication

Is there an alternative to the session feature plugin in servicestack? In some scenarios I cannot use cookies to match the authorized session in my service implementation. Is there a possibility to resolve the session using a token in http header of the request? What is the preferred solution for that in case the browser is blocking cookies?
I'm using ServiceStack without the built-in auth and session providers.
I use a attribute as request filter to collect the user information (id and token), either from a cookie, request header or string parameter.
You can provide this information after the user takes login. You append a new cookie to the response and inject the id and token info on clientside when rendering the view, so you can use for http headers and query parameters for links.
public class AuthenticationAttribute : Attribute, IHasRequestFilter
{
public void RequestFilter(IHttpRequest request, IHttpResponse response, object dto)
{
var userAuth = new UserAuth { };
if(!string.IsNullOrWhiteSpace(request.GetCookieValue("auth"))
{
userAuth = (UserAuth)request.GetCookieValue("auth");
}
else if (!string.IsNullOrEmpty(request.Headers.Get("auth-key")) &&
!string.IsNullOrEmpty(request.Headers.Get("auth-id")))
{
userAuth.Id = request.Headers.Get("id");
userAuth.Token = request.Headers.Get("token");
}
authenticationService.Authenticate(userAuth.Id, userAuth.token);
}
public IHasRequestFilter Copy()
{
return new AuthenticationAttribute();
}
public int Priority { get { return -3; } } // negative are executed before global requests
}
If the user isn't authorized, i redirect him at this point.
My project supports SPA. If the user consumes the API with xmlhttprequests, the authentication stuff is done with headers. I inject that information on AngularJS when the page is loaded, and reuse it on all request (partial views, api consuming, etc). ServiceStack is powerful for this type of stuff, you can easily configure your AngularJS app and ServiceStack view engine to work side by side, validating every requests, globalizing your app, etc.
In case you don't have cookies and the requests aren't called by javascript, you can support the authentication without cookies if you always generate the links passing the id and token as query parameters, and pass them through hidden input on forms, for example.
#Guilherme Cardoso: In my current solution I am using a PreRequestFilters and the built-in session feature.
My workflow/workaround is the following:
When the user gets authorized I took the cookie and send it to the client by using an http header. Now the client can call services if the cookie is set in a http-header (Authorization) of the request.
To achieve this I redirect the faked authorization header to the cookie of the request using a PreRequestFilter. Now I am able to use the session feature. Feels like a hack but works for the moment ;-)
public class CookieRestoreFromAuthorizationHeaderPlugin : IPlugin
{
public void Register(IAppHost appHost)
{
appHost.PreRequestFilters.Add((req, res) =>
{
var cookieValue = req.GetCookieValue("ss-id");
if(!string.IsNullOrEmpty(cookieValue))
return;
var authorizationHeader = req.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.ToLower().StartsWith("basictoken "))
{
var cookie = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Split(' ').Last()));
req.Cookies.Add("ss-id",new Cookie("ss-id",cookie));
req.Items.Add("ss-id",cookie);
}
});
}
}

Communication between AngularJS and a Jersey Webservice which are on a different domain. Can't access correct session

Lately I've been playing around with AngularJS and Java EE 6. I've build an webservice with Jersey and deployed the project on Glassfish. Because I needed some kind of authentication and an OAuth implementation or an JDBCRealm seemed overkill I decided to just create a session if the user successfully logged in.
#POST
#Path("/login")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
public Response login(LoginDAO loginData, #Context HttpServletRequest req) {
req.getSession().invalidate();
loginData.setPassword(PasswordGenerator.hash(loginData.getPassword()));
User foundUser = database.login(loginData);
if(foundUser == null) {
return Response.status(Status.CONFLICT).build();
}
req.getSession(true).setAttribute("username", foundUser.getUsername());
return Response.ok().build();
}
#GET
#Path("/ping")
public Response ping(#Context HttpServletRequest req) {
if(req.getSession().getAttribute("username") == null) {
return Response.ok("no session with an username attribute has been set").build();
}
return Response.ok(req.getSession(true).getAttribute("username")).build();
}
This seems to work alright, if I post to /login from Postman or from a basic jQuery webpage deployed on glassfish I do get the correct username back and a session has been placed. If I then send a GET request to /ping I do get the username back from which I logged in.
I've an AngularJS application deployed on a node.js webserver which needed to login. Because this server is on another port its on another domain and I had to go through the pain of enabling cors. I did this by building a container response filter which sets the response headers.
public class CrossOriginResourceSharingFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest creq, ContainerResponse cresp) {
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost:8000");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Credentials", "true");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
return cresp;
}
}
This did made it possible for me to send different types of HTTP requests from AngularJS to Java EE 6 application deployed on glassfish.
The problem is that when I send a POST request from AngularJS to the /login method, a session is created and I do get my username back. But when I send a GET request to the /ping method I get the "no session with an username attribute has been set" notice.
I believe this has to do with cross domain prevention and that I've to set the withCredentials tag when I send a xhr request. I've been trying to do this in AngularJS but haven't found out how to do this.
function LoginCtrl($scope, $http) {
$scope.login = function() {
$http.post("glassfish:otherport/api/login", $scope.credentials).
success(function(data) {
console.log(data);
}).
error(function(data, error) {
console.log(error);
});
};
};
And in another controller:
$scope.getUsername = function() {
$http.get("glassfish:otherport/api/ping", {}).
success(function(data) {
$scope.username = data;
}).
error(function() {
$scope.username = "error";
})
}
I've tried to set withCredentials is true
$http.defaults.withCredentials = true;
This however didn't solve my problem. I also tried to send it with every request in the config parameter but this didn't solve my problem either.
Depending on the version of AngularJS you are using you might have to set it on each $http.
Since 1.2 you can do:
$http.get(url,{ withCredentials: true, ...})
From 1.1.1 you can globally configure it:
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
If you're using an older version of Angular, try passing a config object to $http that specifies withCredentials. That should work in versions before 1.1:
$http({withCredentials: true, ...}).get(...)
See also mruelans answer and:
https://github.com/angular/angular.js/pull/1209
http://docs.angularjs.org/api/ng.$http
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#section_5
just an update to #iwein anwser, that we can now set in config itself
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
https://github.com/angular/angular.js/pull/1209
(available only after unstable version: 1.1.1)
In 1.2 version, this doesn't work for me:
$http({withCredentials: true, ...}).get(...)
if I read the doc, the shortcut method should take the config object
$http.get(url,{ withCredentials: true, ...})
$http is a singleton, That's the only way to mix in a same application requests with and without credentials.