I have an Aurelia SPA that connects to a ASP.NET Core backend. I use Auth0 for authentication (using aurelia-auth, not the Auth0 Lock widget).
I currently log in with Auth0 directly, not using the SPA. This gives me issues because the Auth0 implementation in my API expects the id_token and not the access_token. This issue can be passed by telling aurelia-auth to use the id_token as access token. But this complicates further communication between Auth0 and the Aurelia app. Auth0 expects the access_token for user profile calls and such.
Should I authenticate via my own API instead? Or should I make two different fetch-clients in Aurelia? One for calling my API (using the id_token) and one for calling the Auth0 API (using the access_token).
I've written a number of blogs on the subject, and I'll link them below for further reading. My recommendation is to create a separate "authentication" root viewModel that is available to all users, distinct from your "app" root viewModel which is available to only logged in users.
main.js
import AuthService from 'AuthService';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
// After starting the aurelia, we can request the AuthService directly
// from the DI container on the aurelia object. We can then set the
// correct root by querying the AuthService's isAuthenticated method.
aurelia.start().then(() => {
var auth = aurelia.container.get(AuthService);
let root = auth.isAuthenticated() ? 'app' : 'login';
aurelia.setRoot(root);
});
}
Further reading
Aurelia Authentication Best Practices, Multiple Shells
Aurelia Authentication Best Practices, Sessions
Sentry, an Aurelia Authentication Template
Related
I have an IdentityServer4 identity provider server. For the most part, I am using the template code from their repo. I am trying to add Google sign-in. I configured the GoogleSignIn in startup and added ClientId/ClientSecret.
When I don't configure the return URIs in the GCP project I get the following error from Google:
"The redirect URI in the request, https://localhost:44333/signin-google, does not match the ones authorized for the OAuth client. To update the authorized redirect URIs..."
When I add the URI
Then as soon as I call Challenge I immediately get a failed callback from Google.
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
// Here, result.Succeeded is false
// Rest of the method...
}
What could be the problem?
If you have made it to the Callback method, then it sounds like Google auth has completed successfully. However, since the IdentityConstants.ExternalScheme cookie is not present, it sounds like you might have a little misconfiguration.
Once the Google authentication handler has completed, it will sign in using the auth scheme set in its SignInScheme property or the default sign-in scheme. It stores the claims from Google into a local auth method, such as a cookie.
What scheme is the Google authentication handler configured to use? If you're using the quickstarts, it may be using IdentityServerConstants.ExternalCookieAuthenticationScheme rather than ASP.NET Identity's IdentityConstants.ExternalScheme that you are looking for.
I'm implementing Azure AD B2C in a new ASP.NET Core 2.1 app.
I've already created the Azure AD B2C tenant and registered my app, etc.
After I login, I get redirected to the URL I specify and I see the token in the URL but I get an error stating the app requires authentication -- see below:
I saw a few similar posts and what I gather is that the token is automatically validated by the middleware. Is that not so?
What do I need to do at this point?
The code I included in my app are as follows:
In ConfigureServices() method:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
In Configure() method in Startup.cs, all I have is app.UseAuthentication();
In my controller, I'm also using [Authorize] to make sure my actions are not open to anonymous users.
What am I missing? How do validate the token and get the claims?
Combining our discussion here as an answer.
The typical approach to this kind of app is that you allow unauthenticated clients to download the HTML, JS, and other static content.
Then the front-end can use MSAL.JS to authenticate the user.
The front-end SPA will get an Id token which tells the front-end who the user is.
MSAL.JS also allows you to get access tokens to call APIs.
It uses hidden iframes + the Implicit Grant flow to do this.
That access token will then need to be attached to requests to the API as a header (Authorization: Bearer token-goes-here).
MSAL.JS will use session or local storage to store the tokens (this is configurable).
So no cookies are used in this setup.
Then the back-end API should authenticate the access token it receives in the header.
What you have there is already sufficient to authenticate the token.
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
The JWT Bearer authentication handler will load the OpenId Connect metadata document on startup from the authority configured here.
That allows it to get the B2C tenant's public signing keys among other things.
This info allows the handler to then validate access tokens as they come in without interacting with B2C in any way.
It checks the signature is valid, the issuer is valid, and that the audience in the token is what has been configured.
Authorization is of course not handled by the authentication handler, so you must then also check that the calling user actually is allowed to access the resource they are accessing.
The user id is available in the access token.
MSAL.JS may have done some validation on the token as well (I can't remember right now if it did), but doing validation in the front-end is something that can be worked around by anyone with control of the user's browser.
Validation on the API side is the most important piece.
I want to connect my angular2 frontend app with symfony backend. So I'm using FOSOAuthServerBundle (https://github.com/FriendsOfSymfony/FOSOAuthServerBundle) to authorize my frontend app, but I don't understand clearly how to implement this.
I tried "token" endpoint method, but there I had to send client_id and client_secret, from my angular2 app. And I think it's bad to store client_secret in public.
"Authorize" endpoint don't use client_secret, but is demanding login form, what is not good for my case.
I tried custom grant extension, but FOSOAuthServerBundle also requires to validate client with client_secret.
What is best practice authorize angular2 with symfony? It's ok to store client_secret in frontend? Or should I extend FOSOAuthServerBundle and remove client_secret checking?
You're correct about client_secret. It's not valid practice to publish secret key more widely.
Unfortunately at this moment FOSOAuthBundle is not suitable to your needs. This bundle focus only about backend OAuth clients. They have open issue on github to add support of public clients: https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/issues/266
One thing to clarify regarding token & authorize endpoints - token and authorize endpoints have to be mixed in process of access to your resource. I suggest you to read whole RFC to understand process of authorization with OAuth: https://www.rfc-editor.org/rfc/rfc6749
Hacky solution: in src/Entity/OAuth2/Client.php you can overwrite the checkSecret method like this:
public function checkSecret($secret)
{
if (in_array("authorization_code", $this->getAllowedGrantTypes())) {
return true;
}
return parent::checkSecret($secret);
}
In the old version of Firebase, my server app written in Java would authenticate with my backend using the secret and the JWT token generator. Then, at anytime, I could call Firebase.getAuth().getToken() and reuse that token in an HTTP call as the auth parameter.
With the new firebase-server-sdk, how would I reuse my limited service account credentials / token with the REST API?
Map<String, Object> auth = new HashMap<String, Object>();
auth.put("uid", "server-app");
FirebaseOptions options = new FirebaseOptions.Builder()
.setDatabaseUrl(Environment.FIREBASE_URL)
.setServiceAccount(MyClass.class.getResourceAsStream("/keys/dev.json"))
.setDatabaseAuthVariableOverride(auth)
.build();
FirebaseApp.initializeApp(options);
That all works great when I use the SDK to subscribe / write to certain locations - specifically locations that require a server-app uid. But I use REST in conjunction in my server app, because I want my server app to make synchronous reads, something Firebase only supports through the REST API.
FirebaseRestClient firebaseRest = new RestAdapter.Builder()
.setEndpoint(Environment.FIREBASE_URL)
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(final RequestFacade request) {
request.addQueryParam("access_token", FirebaseAuth.getInstance().createCustomToken("server-app"));
}
})
.build().create(FirebaseRestClient.class);
I've tried adding both the access_token and auth param. It seems like that createCustomToken method produces a valid JWT, but the REST API isn't responding.
When I pass in that createCustomToken return value as the auth param, I get the following message:
"error" : "Missing claim 'kid' in auth header."
When I pass in that createCustomToken return value as the access_token param, I get the basic Permission denied API response.
Is there an easy way to reuse my existing firebase-server-sdk credentials in a REST API call?
The token you're attempting to use is a Firebase Authentication ID token - the type which is designed to be passed to the Firebase SDK on a client. The REST API accepts a Firebase access token (just like the ones in previous Firebase clients).
Your authentication is failing because normally the Firebase SDK takes care of turning your ID token into an access token. Your server can not do this transition or generate an access token using the Firebase SDK so I recommend using the original Firebase Token Generator library with your Firebase Secret to create access tokens for the REST API. This will work fine even for new Firebase projects created since the I/O release.
Note: In the Console your Database Secret can be found under (Gear Icon) > Project Settings > Database.
I'm sure this is not advised, given the name of the undocumented property, but...
If your SDK has logged in with a serviceAccount, you can use firebase.auth().INTERNAL.getToken(), which returns a promised accessToken (and expirationTime) which then works with the ?access_token parameter.
I am using ASP.NET SPA template, but remaking it to work with AngularJS instead of KnockoutJS. The thing, I have trouble with, is with authorization access token. By default in this template, authorization access token is retrieved, by redirecting user to /Account/Authorize?client_id=web&response_type=token, after that, user is redirected to home with /#access_token=TOKEN_HERE&token_type=bearer&expires_in=1209600 as an arguments. In KO template, there is this common object with function getFragment which reads this url argument, and returns this access token. In template, there is this code, which executes every time page is reloaded
if (!dataModel.getAccessToken()) {
// The following code looks for a fragment in the URL to get the access token which will be
// used to call the protected Web API resource
var fragment = common.getFragment();
if (fragment.access_token) {
// returning with access token, restore old hash, or at least hide token
window.location.hash = fragment.state || '';
dataModel.setAccessToken(fragment.access_token);
} else {
// no token - so bounce to Authorize endpoint in AccountController to sign in or register
window.location = "/Account/Authorize?client_id=web&response_type=token&state=" + encodeURIComponent(window.location.hash);
}
}
dataModel.get/setAccessToken stores token as a variable in LocalStorage.
I was trying to plug this piece of code into AngularJS controller, redirect user, if there is no access token in localstorage. But if I use ngRoute, it messes with response. It changes URL to default route, set in app.Config, and it clears returned token from URL before I can read it.
Can you help me figure out, how and where should I really use this?
This is kind of a generic solution, I'd rip out the ASP.NET SPA stuff. Maybe with Knockout the ASP.NET SPA template provided some value but Angular can do all of what you're trying to do without having to fight your server side template.
Ideally your Angular application is treated like any other client (Android or iOS) to your backend services. Your backend API can expose all the functionality you need, including authentication - there's no reason to pull your user out of your Angular application just to authenticate.
If you would like to bootstrap your application an alternative to ASP.NET SPA would be to use a Yeoman generator. Try out the Angular Fullstack generator, it does a pretty good job of demonstrating how you can authenticate a user within your Angular application.