I have a local ASP.Net FrameWork WebAPI server with the following controller:
public class ValuesController : ApiController
{
// GET api/values
[AuthorizationFilter]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
I created an AuthorizationFilter attribute to handle authorization (only for GET with no id action):
public class AuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext ctx)
{
if(ctx.Request.Headers.Authorization == null)
{
ctx.Response = ctx.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
} else
{
string authenticationString = ctx.Request.Headers.Authorization.Parameter;
string decodedAuthString = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationString));
string username = decodedAuthString.Split(':')[0];
string password = decodedAuthString.Split(':')[1];
// assume that I have checked credentials from DB
if (username=="admin" && password=="root")
{
// authorized...
} else
{
ctx.Response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Also, I modified Web.config to allow CORS:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
...
</system.webServer>
I ran the server and tried to get /api/values/1 from the browser, and it worked.
I then tried to access the action that requires authorization: /api/values :
I used Insomnia to send requests and test CORS. First I tried the action that doesn't require authorization:
Then I tried the action with authorization:
Then I tried the same action but after adding the authentication username and password, and that worked out fine:
After this point, I knew my webapi is configured correctly, so I tried to pull data from a React app using axios:
const api = axios.create({
baseURL: "http://localhost:50511/api",
});
const response = await api.get("/values/1");
console.log(response.data); // works fine: I get "value" as expected
And now, the final step, to configure axios to request the action that requires authentication:
const api2 = axios.create({
baseURL: "http://localhost:50511/api",
auth : {
username: "admin",
password: "root"
}
});
const response = await api2.get("/values"); // causes a network exception
The reported error is strange, since it talks about CORS. I don't get it. If there shall be an error, it can imagine it being an error related to authorization. Not CORS. Not after being able to use axios to pull data from the action that has no authentication filter.
I examined the request header to make sure that it was configured with the correct Authorization parameter:
I also tried to use axios in different ways:
const response1 = await axios.get("http://localhost:50511/api/values",{
auth: {
username: "admin",
password: "root"
}
});
const response2 = await axios.get("http://localhost:50511/api/values",{
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Both of these attempts did not work.
Then I tried again, but this time passing an empty object as the second parameter to the axios call:
const response3 = await axios.get("http://localhost:50511/api/values", {}, {
auth: {
username: "admin",
password: "root"
}
});
const response4 = await axios.get("http://localhost:50511/api/values", {}, {
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Again, none of these attempts worked. What am I don't wrong?
Related
I hope this has not already been asked, I can't seem to find what I need. I have a VUE 3 app and am using a .NET Core Web API to retrieve data from a service. In the Vue app I make an axios call to log in the user
await axios({
method: 'post',
url: 'https://localhost:44345/api/Authentication/SignIn',
contentType: "application/json",
params: {
username: signInData.value.username,
password: signInData.value.password,
keepMeSignedIn: signInData.value.keepMeSignedIn
}
}).then(response => {
if (response.data.succeeded) {
console.log("Result: ", response.data.data);
}
else {
emit('handleServerSideValidationErrors', response);
}
This then calls my API where I call the service to sign in the user. Once I have verified the information and have the user data it is getting set in session.
public void Set<T>(string key, T value)
{
if (key.IsNullOrEmpty())
{
throw new Exception("The key parameter for SessionUtil.Set is required. It cannot be null/empty.");
}
else
{
this._validateSessionObjectVersion();
if (value == null)
{
Remove(key);
}
else
{
string json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
_httpContextAccessor.HttpContext.Session.SetString(key, json);
}
}
}
The issue I am running into is, when I go to another page that needs to access this session it is null. The API calls this get method but is null.
public T Get<T>(string key)
{
T value = default(T);
if (key.IsNullOrEmpty())
{
return value;
}
if (_httpContextAccessor.HttpContext == null)
{
return value;
}
this._validateSessionObjectVersion();
string json = _httpContextAccessor.HttpContext.Session.GetString(key);
if (!json.IsNullOrEmpty())
{
value = JsonConvert.DeserializeObject<T>(json);
}
return value;
}
My Vue app is running on localhost:5001 while my API is running on localhost:44345. I do have a cors policy already in place which allows me to call the API but I don't see what I need to do in order to not lose session.
Turns out my issue was I had set the cookie option of SameSite to SameSiteMode.Lax. As soon as I changed it to SameSiteMode.None it was working for me.
I want to protect all routes from my NextJs website to authenticated users with NextAuth except the login route that can be accessed with being authenticated, I tried making a middleware that checks for user token, and redirect them to the login route if it was unavailable. but a general middleware for that would stop the website from getting public files so i came up with this:
// middleware.js
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt'
export default async function middlware(req) {
// ignoring file system and public folder
if (req.nextUrl.pathname.startsWith('/_next/') ||
req.nextUrl.pathname.startsWith('/icons/') ||
req.nextUrl.pathname.startsWith('/fonts/')) {
return;
}
// igonring api/auth
if (req.nextUrl.pathname.startsWith('/api/auth')) {
return;
}
// user session
const session = await getToken({ req })
// redirecting to homepage if a logged in user tries to access the login page
if (req.nextUrl.pathname.startsWith('/auth/login')) {
if (session) {
const url = req.nextUrl.clone()
url.pathname = '/'
return NextResponse.redirect(url)
}
}
// // redirecting to login page if not logged in
if (!req.nextUrl.pathname.startsWith('/auth/login')) {
if (!session && session == null) {
const url = req.nextUrl.clone()
url.pathname = '/auth/login'
return NextResponse.redirect(url)
}
}
}
this works for general routes, but when I try to fetch some data in other pages from other API routes I get this error:
FetchError: invalid json response body at http://localhost:3000/auth/login reason: Unexpected token < in JSON at position 0
at E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\compiled\node-fetch\index.js:1:51227
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async getServerSideProps (webpack-internal:///./src/pages/departments-marking.js:70:22)
at async Object.renderToHTML (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\render.js:490:20)
at async doRender (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\base-server.js:908:38)
at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\base-server.js:1013:28)
at async E:\0 - WEB\xwendkaran-admin\node_modules\next\dist\server\response-cache.js:69:36 {
type: 'invalid-json'
}
this error only occurs because of the middleware and if i remove the middle everything works but that means anyone can access the website and also post data to the server
The problem occurred because when I was trying to fetch data from an API route there was no cookie with the request headers, and !session && session == null would stop it because it showed as not authenticated, so I fixed it with adding user cookies with request headers while fetching the data, like this:
export async function getServerSideProps({ req }) {
try {
// fetching centers
const data = await fetch(`${server}/api/API-ROUTE`, {
method: 'GET',
headers: {
'cookie': req.headers.cookie, // this is where I parsed the cookies
'Content-Type': 'application/json',
'Accept': 'application/json'
},
}).then(res => res.json())
return {
props: {
data
}
}
}
catch (error) {
console.log(error.message)
return {
props: {}
}
}
}
Hi everyone!
I have my own custom strategy to get token, and all is good, but when a refresh page I lose user data and fetchUser does not works. It doesn´t send the params to API to get again the user data.
the workflow is next:
1- send params to token api and get token
2- send params to login API to get the user
//nuxt.config.js
customStrategy: {
_scheme: '~/schemes/customScheme',
endpoints: {
login: {
url: '/api/v1/token',
method: 'post',
propertyName: 'token',
headers: {'x-channel-id': 1}
},
user: {
url: '/api/v1/login',
method: 'post',
propertyName: false,
headers: {'x-channel-id': 1}
},
logout: null
}
}
customScheme.js
import LocalScheme from '#nuxtjs/auth/lib/schemes/local'
export default class CustomScheme extends LocalScheme {
_setToken (token) {
if (this.options.globalToken) {
// Set Authorization token for all axios requests
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
}
}
_clearToken () {
if (this.options.globalToken) {
// Clear Authorization token for all axios requests
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
}
}
mounted () {
if (this.options.tokenRequired) {
const token = this.$auth.syncToken(this.name)
this._setToken(token)
}
return this.$auth.fetchUserOnce()
}
async login (endpoint) {
if (!this.options.endpoints.login) {
return
}
// Get token
const result = await this.$auth.request({
...endpoint
},
this.options.endpoints.login
)
// Set token
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + result
: result
this.$auth.setToken(this.name, token)
this._setToken(token)
}
// If result I get and set user
if (result) {
const user = await this.$auth.request({
...endpoint
},
this.options.endpoints.user
)
this.$auth.setUser(user);
}
}
async fetchUser (endpoint) {
// User endpoint is disabled.
if (!this.options.endpoints.user) {
this.$auth.setUser({})
return
}
// Token is required but not available
if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
return
}
// Try to fetch user and then set
try{
const user = await this.$auth.requestWith(
this.name,
endpoint,
this.options.endpoints.login
)
this.$auth.setUser(user)
} catch (error){
console.log(error)
}
}
}
When I set this.$auth.setUser(user) in login() method all is fine and app redirect me to /dashboard page and the user information (like role and email) is displayed on navBar but when I refresh page I lose user data. The app try to fetchUser but it give me a 400 error because user and password not sent.
Another thing I don´t understand is Why endpoint parameter is undefined in async fetchUser (endpoint) ??? . I think there is an issue in this part.
I hope u can help me
Regards
I just remove all this library and did my own custom Nuxt authentication
https://nemanjadragun92.medium.com/nuxt-js-custom-authentication-245d2816c2f3
I'm triggering a HTTP request and I'm getting a valid response from it. The response also has a header X-Token that I wish to read. I'm trying the below code to read the headers, however, I get null as a result
this.currentlyExecuting.request = this.http.request(reqParams.type, reqParams.url, {
body: reqParams.body,
responseType: 'json',
observe: 'response'
}).subscribe(
(_response: any) => {
// Also tried _response.headers.init();
const header = _response.headers.get('X-Token');
console.log(header);
onComplete(_response.body);
},
_error => {
onComplete({
code: -1,
message: Constants.WEBSERVICE_INTERNET_NOT_CONNNECTED
});
}
);
The response of the API, when checked in Chrome inspect, shows the header is present.
Have you exposed the X-Token from server side using access-control-expose-headers? because not all headers are allowed to be accessed from the client side, you need to expose them from the server side
Also in your frontend, you can use new HTTP module to get a full response using {observe: 'response'} like
http
.get<any>('url', {observe: 'response'})
.subscribe(resp => {
console.log(resp.headers.get('X-Token'));
});
In my case in the POST response I want to have the authorization header because I was having the JWT Token in it.
So what I read from this post is the header I we want should be added as an Expose Header from the back-end.
So what I did was added the Authorization header to my Exposed Header like this in my filter class.
response.addHeader("Access-Control-Expose-Headers", "Authorization");
response.addHeader("Access-Control-Allow-Headers", "Authorization, X-PINGOTHER, Origin, X-Requested-With, Content-Type, Accept, X-Custom-header");
response.addHeader(HEADER_STRING, TOKEN_PREFIX + token); // HEADER_STRING == Authorization
And at my Angular Side
In the Component.
this.authenticationService.login(this.f.email.value, this.f.password.value)
.pipe(first())
.subscribe(
(data: HttpResponse<any>) => {
console.log(data.headers.get('authorization'));
},
error => {
this.loading = false;
});
At my Service Side.
return this.http.post<any>(Constants.BASE_URL + 'login', {username: username, password: password},
{observe: 'response' as 'body'})
.pipe(map(user => {
return user;
}));
You should use the new HttpClient. You can find more information here.
http
.get<any>('url', {observe: 'response'})
.subscribe(resp => {
console.log(resp.headers.get('X-Token'));
});
As Hrishikesh Kale has explained we need to pass the Access-Control-Expose-Headers.
Here how we can do it in the WebAPI/MVC environment:
protected void Application_BeginRequest()
{
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
//These headers are handling the "pre-flight" OPTIONS call sent by the browser
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "*");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:4200");
HttpContext.Current.Response.AddHeader("Access-Control-Expose-Headers", "TestHeaderToExpose");
HttpContext.Current.Response.End();
}
}
Another way is we can add code as below in the webApiconfig.cs file.
config.EnableCors(new EnableCorsAttribute("", headers: "", methods: "*",exposedHeaders: "TestHeaderToExpose") { SupportsCredentials = true });
**We can add custom headers in the web.config file as below. *
<httpProtocol>
<customHeaders>
<add name="Access-Control-Expose-Headers" value="TestHeaderToExpose" />
</customHeaders>
</httpProtocol>
we can create an attribute and decore the method with the attribute.
Happy Coding !!
You can get data from post response Headers in this way (Angular 6):
import { HttpClient, HttpHeaders, HttpResponse } from '#angular/common/http';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
observe: 'response' as 'response'
};
this.http.post(link,body,httpOptions).subscribe((res: HttpResponse<any>) => {
console.log(res.headers.get('token-key-name'));
})
You can get headers using below code
let main_headers = {}
this.http.post(url,
{email: this.username, password: this.password},
{'headers' : new HttpHeaders ({'Content-Type' : 'application/json'}), 'responseType': 'text', observe:'response'})
.subscribe(response => {
const keys = response.headers.keys();
let headers = keys.map(key => {
`${key}: ${response.headers.get(key)}`
main_headers[key] = response.headers.get(key)
}
);
});
later we can get the required header form the json object.
header_list['X-Token']
Angular 7
Service:
this.http.post(environment.urlRest + '/my-operation',body, { headers: headers, observe: 'response'});
Component:
this.myService.myfunction().subscribe(
(res: HttpResponse) => {
console.log(res.headers.get('x-token'));
} ,
error =>{
})
Try this simple code.
1. Components side code: to get both body and header property. Here there's a token in body and Authorization in the header.
loginUser() {
this.userService.loginTest(this.loginCred).
subscribe(res => {
let output1 = res;
console.log(output1.body.token);
console.log(output1.headers.get('Authorization'));
})
}
2. Service side code: sending login data in the body and observe the response in Observable any which be subscribed in the component side.
loginTest(loginCred: LoginParams): Observable<any> {
const header1= {'Content-Type':'application/json',};
const body = JSON.stringify(loginCred);
return this.http.post<any>(this.baseURL+'signin',body,{
headers: header1,
observe: 'response',
responseType: 'json'
});
}
I had to do the following to get the headers to appear in SPA Angular application when GETting them from ASP.NET Core service:
var builder = WebApplication.CreateBuilder(args);
services.AddCors(options =>
{
options.AddPolicy("MyExposeResponseHeadersPolicy",
builder =>
{
builder.WithOrigins("https://*.example.com")
.WithExposedHeaders("x-custom-header");
});
});
builder.Services.AddControllers();
var app = builder.Build();
I'm using Microsoft Owin and ASP.NET WebApi for authentication and authorization process for my client application. Also the authentication server is secured by HTTPS. I've read a few articles about using Microsoft Owin, one of them which I've chosen to implement is:
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
There are some differences between my project and that implementation:
I need to identify my client in case the request is sent by my application on the mobile phone, not any other devices or tools like Fiddler. I think one the options could be sending an application id by each request from the mobile application. But I don't know how and where should I verify the requests in authentication server application. This is really important for registering users:
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(UserModel userModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
IdentityResult result = await _repo.RegisterUser(userModel);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
I don't want to let unreliable devices, i.e. the clients except the mobile application, to call this method.
I need to let anonymous users to buy some products from the website, but I don't know what is the best practice to issue token for anonymous users without doing authentication.
If you want to identify your client and authorize it you can override the method ValidateClientAuthentication.
In Taiseer's example you have linked you will find some code:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
and a note which says:
As you notice this class inherits from class
“OAuthAuthorizationServerProvider”, we’ve overridden two methods
“ValidateClientAuthentication” and “GrantResourceOwnerCredentials”.
The first method is responsible for validating the “Client”, in our
case we have only one client so we’ll always return that its validated
successfully.
If you want to validate the client you have to put some logic in there.
Normally you would pass a clientId and a clientSecret in the header of your http request, so that you can validate the client's request with some database parameters, for example.
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
return;
}
try
{
// You're going to check the client's credentials on a database.
if (clientId == "MyApp" && clientSecret == "MySecret")
{
context.Validated(clientId);
}
else
{
// Client could not be validated.
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
catch (Exception ex)
{
string errorMessage = ex.Message;
context.SetError("server_error");
context.Rejected();
}
return;
}
In the sample above you will try to extract the client credentials sent in the header of your request:
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
and validated them:
// You're going to check the client's credentials on a database.
if (clientId == "MyApp" && clientSecret == "MySecret")
{
context.Validated(clientId);
}
if the client is sending a wrong request header you need to reject the request:
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
The method ValidateClientAuthentication is processed before GrantResourceOwnerCredentials. This way you can extend it and pass GrantResourceOwnerCredentials some extra information you might need there.
In one of my applications I've created a class:
class ApplicationClient
{
public string Id { get; set; }
public string Name { get; set; }
public string ClientSecretHash { get; set; }
public OAuthGrant AllowedGrant { get; set; }
public DateTimeOffset CreatedOn { get; set; }
}
which I use in ValidateClientAuthentication right after I've checked the clientId and the secret are ok:
if (clientId == "MyApp" && clientSecret == "MySecret")
{
ApplicationClient client = new ApplicationClient();
client.Id = clientId;
client.AllowedGrant = OAuthGrant.ResourceOwner;
client.ClientSecretHash = new PasswordHasher().HashPassword("MySecret");
client.Name = "My App";
client.CreatedOn = DateTimeOffset.UtcNow;
context.OwinContext.Set<ApplicationClient>("oauth:client", client);
context.Validated(clientId);
}
As you can see here
context.OwinContext.Set<ApplicationClient>("oauth:client", client);
I am setting a Owin variable which I can read later on. In your GrantResourceOwnerCredentials now you can read that variable in case you need it:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
ApplicationClient client = context.OwinContext.Get<ApplicationClient>("oauth:client");
...
}
Now, if you want to fetch the bearer token - which you're going to use for all the secure API calls - you need to encode your clientId and clientSecret (base64) and pass it in the header of the request:
An ajax request with jquery would look something like this:
var clientId = "MyApp";
var clientSecret = "MySecret";
var authorizationBasic = $.base64.btoa(clientId + ':' + clientSecret);
$.ajax({
type: 'POST',
url: '<your API token validator>',
data: { username: 'John', password: 'Smith', grant_type: 'password' },
dataType: "json",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
xhrFields: {
withCredentials: true
},
headers: {
'Authorization': 'Basic ' + authorizationBasic
},
beforeSend: function (xhr) {
},
success: function (result) {
var token = result.access_token;
},
error: function (req, status, error) {
alert(error);
}
});
As you can see I've also added the username and password - with the grant type - in the body of the request:
data: { username: 'John', password: 'Smith', grant_type: 'password' }
so that the server will be able to validate the client (clientId + clientSecret) and the user (username + password).
If the request is successful you should get back a valid token:
oAuth.Token = result.access_token;
which you can store somewhere for the following requests.
Now you can use this token for all the requests to the api:
$.ajax({
type: 'GET',
url: 'myapi/fetchCustomer/001',
data: { },
dataType: "json",
headers: {
'Authorization': 'Bearer ' + oAuth.Token
},
success: function (result) {
// your customer is in the result.
},
error: function (req, status, error) {
alert(error);
}
});
Another thing you might want to add to your API during the startup is SuppressDefaultHostAuthentication:
config.SuppressDefaultHostAuthentication();
this is an extension method of HttpConfiguration. Since you're using bearer tokens you want to suppress the standard cookie-based authentication mechanism.
Taiseer has written another series of articles which are worth reading where he explains all these things.
I have created a github repo where you can see how it works.
The Web API is self-hosted and there are two clients: jQuery and Console Application.