SPA - Firebase and .Net WebApi 2 authentication - javascript

I'm having a Single Page Application written in AngularJs (The framework is irrelevant at this point) The application is hosted in IIS and it's compose of index.html plus a bunch of client assets.
On backend I have WebApi 2, hosted also in IIS as a separate application.
For authentication on client I'm using Firebase (simple login) with several social netoworks enabled, like Facebook, Twitter or Google.
So far so good. I like how easy it is to enable twitter authentication for example with firebase.
On login with social network i get back from firebase, the firebaseAuthToken and provider accesstoken.
Now I want to use firebaseAuthToken or provider access token to authenticate with my WebApi.
The question is: What is the best way to authenticate with WebApi in given conditions?
There is not an option to use only firebase to store my data and get rid of web api since I have in place complex business logic on server.
One silly idea that i have so far, is to pass the social provider access token to the server, validate the token against provider and then issue a security token using Owin -Katana.
I'm not using build in social providers support from katana due to lack of documentation, complexity and bad integration with single page apps. I found the visual studio template for SPA too mvc specific. But that's me :)

tl;dr - Demo Project on GitHub
The steps below may seem long, but it's actually really easy. I created my demo project in just an hour or so.
I agree with you about using Owin and Katana. I've been through that process before and it wasn't really a great experience. Using Firebase was a heck of a lot easier.
This can all be done with JWTs!
When you authenticate through Firebase and whatever social provider, you get back a JSON Web Token (JWT) - firebaseAuthToken.
Grab your Firebase Secret from the Dashboard
The way JWTs work is that we have a secret token and a client token. The client token is the firebaseAuthToken we receive after logging in. The secret token is generated for us in the Firebase Dashboard.
Store your Firebase Secret in the appSettings section of your Web.config
We need to store this secret key in the Web.config so it's easier to access later.
<add key="FirebaseSecret" value="<Firebase-Secret-Token-Goes-Here" />
Create an Action Filter to check the JWT from the Authorization Header
We can verify the request is valid by passing the client token in the Authorization header. On the server we can store our secret key that we get from our Firebase Dashboard. When the request is checked by Web API we can decode the JWT using a JWT Library (available from NuGet). If the decoding is successful then we can check to token to make sure it isn't expired.
public class DecodeJWT: ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string firebaseAuthToken = string.Empty;
if (actionContext.Request.Headers.Authorization != null) {
firebaseAuthToken = actionContext.Request.Headers.Authorization.Scheme;
} else {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
string secretKey = WebConfigurationManager.AppSettings["FirebaseSecret"];
try {
string jsonPayload = JWT.JsonWebToken.Decode(firebaseAuthToken, secretKey);
DecodedToken decodedToken = JsonConvert.DeserializeObject < DecodedToken > (jsonPayload);
// TODO: Check expiry of decoded token
} catch (JWT.SignatureVerificationException jwtEx) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
} catch (Exception ex) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
base.OnActionExecuting(actionContext);
}
}
Create a $httpInterceptor add the firebaseAuthToken to the header for every request
On the client, the trick is that the token has to be passed every time. To make this easier we need to create a $httpInterceptor with Angular that checks for a firebaseAuthToken on sessionStorage.
.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.firebaseAuthToken) {
config.headers.Authorization = $window.sessionStorage.firebaseAuthToken;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// TODO: User is not authed
}
return response || $q.when(response);
}
};
})
Set the firebaseAuthToken to sessionStorage on a successful login
Whenever a user logs in we can set the value to sessionStorage.
$rootScope.$on('$firebaseSimpleLogin:login',
function (e, user) {
// add a cookie for the auth token
if (user) {
$window.sessionStorage.firebaseAuthToken = user.firebaseAuthToken;
}
cb(e, user);
});
Register the DecodeJWT filter globally
Inside of the WebApiConfig.cs Register method we can set the DecodeJWT filter to apply for all of our ApiControllers.
config.Filters.Add(new DecodeJWT());
Now whenever we make a request to an ApiController it will reject it unless there is a valid JWT. So after a user logs in we can save their data to a ApiController if it already doesn't exist.
// globally uses DecodeJWT
public class UsersController: ApiController
{
// POST api/users
public void Post([FromBody] FbUser user) // See GitHub for this Model
{
// Save user if we do not already have it
}
}

Related

In Nuxt.js how do you restrict some routes only to clients with a valid JWT token?

In Nuxt.js this is one way to implement authentication :
The client authenticates by sending an HTTP request with its credentials in the body to an API route of the Nuxt backend ;
The Nuxt backend responds with a JWT token that allows the client to access protected routes ;
Finally, when the authenticated user tries to access such a route, they make an HTTP request to the Nuxt backend with their JWT token inserted in the header ;
The backend validates the JWT token and responds with the requested page JSON data to the client.
What I don't understand is how to make the Nuxt backend aware that for some protected routes it has to check the JWT token of the client before providing the page JSON data. I mean, where exactly in Nuxt can I implement this kind of validation ?
Well i am confused a bit first you say API data the other sentece you say JSON page.. however. If you want to protect an PAGE then you create an middleware
middleware/auth.js
export default async function ({ store, $axios, redirect }) {
let valid = await $axios.$post('/api/checktoken')
if (!valid) {
redirect('/')
}
}
You need to create an API where you check the token. Usually you need to put the token in your header like Authentication: Bearer token... however i simply save my token inside an cookie. Because if you send an HTTP request to the server the cookies gets automatically sended with it so i dont need to do some extra work.
Next step is you go to some page and set your middleware auth.
page.vue
<script>
export default {
middleware: "auth"
}
</script>
However if you want to protect some backend routes you can do it like this. Create again an middleware
async authenticate(req, res, next) {
let token = await cookieService.getTokenFromCookie(req)
if (!token) return errorService.resError(res, 400, 'Authorization failed')
let tokenValid = await tokenService.verifyToken(token)
if (!tokenValid)
return errorService.resError(res, 400, 'Authorization failed')
let decodedData = tokenService.decode(token)
if (!decodedData)
return errorService.resError(res, 400, 'Authorization failed')
res.locals.userId = decodedData.userId
res.locals.role = decodedData.role
next()
}
In this case you basically need to read the token out of your cookie. (in case you dont use cookies you will need to read it out of your header so for this you should create an function that reads your token out of the header)
Check if token is even there.
Verify if token is valid.
Decode the token so you can access the data in it
Now you can also put the data to your res.locals. The advantage is that this data is scoped to this current request and you can access it in the next middleware / endpoint.
then you call next() to step to the next middleware / endpoint
function endpoint(req, res) {
let { userId, role } = res.locals
do something....
}
So the route looks something like this:
app.use("/some/api/point", authenticate, endpoint)
The good thing about is you can put authenticate in every API route you want to protect.

Front-end Authorization is INSECURE, almost always. Don't you agree?

I’m in web dev since many years ago, but I still do not believe you can easily implement user authorization in front-end apps or browser extensions.
While front-end authentication can be achieved (cookies, jwt, etc.) and works fairly well, the same is not for authorization. The common example is when you want to restrict access to some content and/or functionalities to logged-in users only.
I am inspecting many browser extensions and web apps, and I usually find something like this pseudocode:
if (user.isLogged === true) {
// code to show ui components and actions
} else {
// code to show ui components and actions
}
which is highly insecure.
For instance, this is coming from an extension available on the chrome web store:
function initApp() {
firebase
.auth()
.onAuthStateChanged(function (user) {
if (user) {
// User is signed in. var uid = user.uid; window.location.href = "app.html";
const uid = user.uid;
const name = user.displayName;
getUserData(uid);
// Plus a lot of other code to show/hide ui components and actions
} else { }
document
.getElementById('quickstart-button')
.disabled = false;
});
document
.getElementById('quickstart-button')
.addEventListener('click', startSignIn, false);
}
The only secure way is to actually load HTML and JS chunks dynamically only when and if the user is authorized to access that page/functionality, where the server decides which chunks serve to the current user based on his role, and then those chunks are injected at runtime. But this is, on one hand, failing with the spa architecture itself because it's very near to serving the app server-side (it's never the client to decide what to show). It also requires injecting HTML markup dynamically using JS which is not ideal for security. Plus, this is something not trivial to implement, so I guess the majority of js apps out there are handling restricted content/areas using the paradigm shown above, based on the current in-memory/store (or in cookie/storage) state of the current user, which is highly insecure and can be easily manipulated by the end-user. Especially in browser extensions where you are not even allowed to obfuscate the code.
Am I missing something?
TL;DR you are right, don't rely on front-end security. There is none. Grab the secret content from a backend and require authentication.
You mentioned SPAs so let's analyze the situation there.
Assume you have a React App with a main component looking similar to this:
function App() {
const { loggedIn, isSpecialUser } = useContext(authContext);
if (loggedIn)
return <MainPage />;
if (isSpecialUser)
return <SuperSecretPage />;
return <LoginPage />;
}
The loggedIn and isSpecialUser state is dynamically set after the user logged in (or relogin by cookies, jwt, whatever).
Therefore the restricted areas for MainPage and SuperSecretPage will only show after the user logged in.
So what if the user looks at the source of the SuperSecretPage?
He should see something like this:
function App() {
const { token } = useContext(authContext);
const { data } = useApi(getSuperSecretContent, token);
return <div>{data}</div>;
}
Even if the super secret content is static for all user, it should be fetched with a separate call that requires a token. Access rights must be checked by the backend. If you just write your content in there, it can be seen by everyone.
Edit Response to OP's comment. He wrote
a malicious user has at least 2 options: 1) change the js content of the main App component, to let's say return ; when the user is not logged in at all; 2) manually change the ajax callback to set his status to Superuser regardless of the actual response.
For both ways, the user would just break the clients rendering but not the security. In any case, the client must make an API-call to receive the secret content:
const { data } = useApi(getSuperSecretContent, token);
useApi is an abstract hook that uses some sort of API (e.g. REST-API) to fetch the content during runtime (ajax). The second parameter is a token. This token must be some sort of authentication token the servers sends the user after login. This token here is the crucial part (see OAuth, JWT). For each ajax call to get the super secret content, a valid token must be supplied. The backend then has to check if the token is valid. If and only if the token is valid, the backend returns the super secret content.
Pseudocode useApi:
function useApi(apiCall, token) {
make ajax request {
endpoint: apiCall,
authorization header: token
}
if success:
return data
else if auth error:
return error
}
Pseudocode backend:
get request to super secret content:
if token is not valid:
return auth error;
else
return super secret content
The user cannot trick the backend into sending him the super secret content by manipulating the frontend state.
Edit 2 Response to OP's confusion about authentication and authorization.
You must use the token also for authorization. In case of a JWT for instance, the token itself can include infos about rights management, otherwise you could query access rights from a database for a given user.
This must happen during the request handling #backend side.
Pseudocode backend e.g.:
get request to super secret content:
get access rights for token
if access rights include super secret resources:
return super secret content;
else
return auth error;

C# Owin handle 401 access token errors and regenerate access tokens using refresh token

I 'm new to this forum and I expect a lot of help from the experts, we have a project where OWIN authentication is implemented in C# and we have internal tool for UI(in old angular language, javascript) there seems to be lot of flaws, below are some issues.
If access token gets expired and there is a call to access protected resource, here the call won't complete so how can we make that call complete post generating a new access token using refresh token, this needs to be handled in UI or service side, any code pointers ?
Is it a good practice to generate access token using refresh token before access token expiry interval in UI?
Api's are also consumed by windows service so if access token expires and the service hit's any api with the expired token it will throw unauthorized however same question how to create token on fly and make that call complete.
Any help on this, would be really greatful so, awaiting for your replies, sample code shown below.
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid, context.Ticket);
// hash??
context.SetToken(guid);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
// Now in my Startup.Auth.cs
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider() // This is my test
};
1) I cannot really provide any code pointers since I don't know your Angular version, but the way I have done it up until now is having some sort of Interceptor for your requests. It looks for any Unauthorized status codes coming from the API, caches the request, tries to hit the refresh token endpoint and resends the cached request with the new access token if the refresh request was successful, if the refresh request failed, ask the user to authenticate again.
You can also try to implement an interceptor that inspects the access token expiry date and hits the refresh token endpoint if it has 5 minutes of validity left.
2) I'd say that refresh tokens are typically used for already expired access tokens.
3) I don't know how much control you have over those Windows Services, but to my knowledge, the refresh token flow is supposed to be handled on the client-side.

Migrating master user to service principal for authentication on power bi embedded

I'm trying to migrating my authentication method from Power BI Master User to service principal.
on master user I'm using msal with authentication flow like bellow:
login to AAD --> request for AAD token --> importing pbix file with rest API using AAD token as credential
this is the code
$(document).ready(function () {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
acquireTokenPopup();
});
Msal.UserAgentApplication
});
function acquireTokenPopup() {
myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
AADToken = tokenResponse.accessToken;
importPBIX(AADToken);
});
}
function importPBIX(accessToken) {
xmlHttp.open("GET", "./importPBIX?accessToken=" + accessToken + "&pbixTemplate=" + pbixTemplate, true);
//the rest of import process//
}
so there are two question:
1. what kind of flow would it be if I use service principal instead?
on my head and from the info which I read from microsoft document it would be simpler like:
request token using application secret key --> importing pbix file with rest API using token
is this correct?
2. what kind of code that I can use to do this on javascript?I think MSAL couldn't do token request by using service principal. would appreciate any info or tutorial for this.
bests,
what kind of flow would it be if I use service principal instead? on my head and from the info which I read from microsoft document it would be simpler like: request token using application secret key --> importing pbix file with rest API using token is this correct?
According to my research, if you want to use the service principal to get Azure AD access token, you can use the client credentials grant flow
The client application authenticates to the Azure AD token issuance endpoint and requests an access token.
The Azure AD token issuance endpoint issues the access token.
The access token is used to authenticate to the secured resource.
Data from the secured resource is returned to the client application.
Regarding how to get access token, please refer to the following steps
Register Azure AD application
Configure API permissions
Get access token
POST https://login.microsoftonline.com/<tenant id>/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=<>
&client_secret=<>
&resource=https://analysis.windows.net/powerbi/api
2. what kind of code that I can use to do this on javascript?I think MSAL couldn't do token request by using service principal. would appreciate any info or tutorial for this.
If you want to implement client credentials grant flow with sdk, you can use adal-node. For more details, please refer to https://www.npmjs.com/package/adal-node.
For example
var AuthenticationContext = require('adal-node').AuthenticationContext;
var authorityHostUrl = 'https://login.microsoftonline.com/';
var tenant = 'myTenant.onmicrosoft.com'; // AAD Tenant name.
var authorityUrl = authorityHostUrl + '/' + tenant;
var applicationId = 'yourApplicationIdHere'; // Application Id of app registered under AAD.
var clientSecret = 'yourAADIssuedClientSecretHere'; // Secret generated for app. Read this environment variable.
var resource = ''; // URI that identifies the resource for which the token is valid.
var context = new AuthenticationContext(authorityUrl);
context.acquireTokenWithClientCredentials(resource, applicationId, clientSecret, function(err, tokenResponse) {
if (err) {
console.log('well that didn\'t work: ' + err.stack);
} else {
console.log(tokenResponse);
}
});
thanks to Jim's answer, I've tweaked my code a little bit and the token authentication process went smoothly.
As my apps using javascript at front-end and python as its back-end, I decided to do the process at python and used python msal library instead.
the code is just like :
authority_host_uri = 'https://login.microsoftonline.com'
tenant = 'myTenantId'
authority_uri = authority_host_uri + '/' + tenant
client_id = 'myClienId'
client_secret = 'myClientSecretKey'
config={"scope":["https://analysis.windows.net/powerbi/api/.default"]}
app = ConfidentialClientApplication(client_id, client_credential=client_secret, authority=authority_uri)
token = app.acquire_token_for_client(scopes=config['scope'])
once again thanks to Jim for helping me on this one.
bests,

Angular against Asp.Net WebApi, implement CSRF on the server

I'm implementing a website in Angular.js, which is hitting an ASP.NET WebAPI backend.
Angular.js has some in-built features to help with anti-csrf protection. On each http request, it will look for a cookie called "XSRF-TOKEN" and submit it as a header called "X-XSRF-TOKEN" .
This relies on the webserver being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.
The Angular documentation states:
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header, and therefore be sure that only JavaScript running on your domain could have read the token. The token must be unique for each user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). We recommend that the token is a digest of your site's authentication cookie with salt for added security.
I couldn't find any good examples of this for ASP.NET WebAPI, so I've rolled my own with help from various sources. My question is - can anyone see anything wrong with the code?
First I defined a simple helper class:
public class CsrfTokenHelper
{
const string ConstantSalt = "<ARandomString>";
public string GenerateCsrfTokenFromAuthToken(string authToken)
{
return GenerateCookieFriendlyHash(authToken);
}
public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken)
{
return csrfToken == GenerateCookieFriendlyHash(authToken);
}
private static string GenerateCookieFriendlyHash(string authToken)
{
using (var sha = SHA256.Create())
{
var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
return cookieFriendlyHash;
}
}
}
Then I have the following method in my authorisation controller, and I call it after I call FormsAuthentication.SetAuthCookie():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
// http://docs.angularjs.org/api/ng.$http
private void SetCsrfCookie()
{
var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
Debug.Assert(authCookie != null, "authCookie != null");
var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
HttpContext.Current.Response.Cookies.Add(csrfCookie);
}
Then I have a custom attribute which I can add to controllers to make them check the csrf header:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
// http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
protected override bool IsAuthorized(HttpActionContext context)
{
// get auth token from cookie
var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
if (authCookie == null) return false;
var authToken = authCookie.Value;
// get csrf token from header
var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (String.IsNullOrEmpty(csrfToken)) return false;
// Verify that csrf token was generated from auth token
// Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header.
// This proves that our site made the request.
return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
}
}
Lastly, I clear the Csrf token when the user logs out:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Can anyone spot any obvious (or not-so-obvious) problems with that approach?
Your code seems to be fine. The only thing is, you don't need most of the code you have as web.api runs "on top" of asp.net mvc, and latter has built in support for anti-forgery tokens.
In comments dbrunning and ccorrin express concerns that you only able to use build in AntiForgery tokens only when you are using MVC html helpers. It is not true. Helpers can just expose session based pair of tokens that you can validate against each other. See below for details.
UPDATE:
There is two methods you can use from AntiForgery:
AntiForgery.GetTokens uses two out parameters to return cookie token and form token
AntiForgery.Validate(cookieToken, formToken) validates if pair of tokens is valid
You totally can repurpose those two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call validate on both within attribute.
Another solution is to use JWT (check eg MembershipReboot implementation)
This link shows how to use built in anti-forgery tokens with ajax:
<script>
#functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajax("api/values", {
type: "post",
contentType: "application/json",
data: { }, // JSON data goes here
dataType: "json",
headers: {
'RequestVerificationToken': '#TokenHeaderValue()'
}
});
</script>
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
Also take a look at this question AngularJS can't find XSRF-TOKEN cookie
This solution isn't secure since CSRF attacks are still possible as long as the Auth cookie is valid. Both the auth and the xsrf cookie will be sent to the server when an attacker makes you perform a request via another site, and therefore you are still vulnerable until the user does a "hard" logout.
Each request or session should have its own unique token to truly prevent CRSF attacks. But probably the best solution is to not use cookie based authentication but token based authentication such as OAuth. This prevents other websites from using your cookies to perform unwanted requests, since the tokens are used in http headers instead of cookies. And http headers are not automatically send.
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity
These excellent blog posts contain information of how to implement OAuth for WebAPI. The blog posts also contains great information of how to integrate it with AngularJS.
Another solution might be to disable CORS and only accept incoming requests from whitelisted domains. However this won't work for non-website applications, such as mobile and/or desktop clients. Next to that once your website is vulnerable to a XSS attack the attacker will still be able to forge requests on your behalve.
I think your code is flawed. The whole idea around prevent CSRF is to prevent a unique token on each REQUEST, not each session. If the anti-forgery token is a session persisted value, the ability to perform CSRF still remains. You need to provide a unique token on each request...
Haven't had any problems pointed out with the code, so I consider the question answered.

Categories