Simple ember-cli-simple-auth and oauth2 project - authentication

I'm very new to 3rd party authentication and have not been able to make an API request to LinkedIn through the ember-cli-simple-auth addon paired with simple-auth-oauth2. My environment.js is below, where I've tried to piece together parts from Simple Labs' introduction1 and http://ember-simple-auth.com/ember-simple-auth-api-docs.html1, specifically at the bottom of where environment === 'test'. I don't get any errors after building the CLI project but I do get a notification in the browser's console (not warning or error) that says:
No authorizer was configured for Ember Simple Auth - specify one if backend requests need to be authorized.
Right now, I'm just trying to get an auth token back from LinkedIn and am not sure what the next step is. If I try to trigger the "authenticate" action on my login controller (extending the loginControllerMixin), I get an error in the console saying:
Uncaught Error: Assertion Failed: No authenticator for factory "authenticator:simple-auth-oauth2" could be found
What part or parts did I miss?
Environment.js:
// config/environment.js
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'seminars-me',
environment: environment,
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
// ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.baseURL = '/';
ENV.locationType = 'none';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
ENV['simple-auth-oauth2'] = {
serverTokenEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=757ll7ci1xd93u&scope=profile'
};
ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:oauth2-bearer',
crossOriginWhitelist: ['https://www.linkedin.com'],
store: 'simple-auth-session-store:local-storage'
};
}
if (environment === 'production') {
}
return ENV;
};

The authenticator is actually registered as simple-auth-authenticator:oauth2-password-grant while you're using authenticator:simple-auth-oauth2.

Related

Standalone Blazor WebAssembly authentication very slow [duplicate]

I would like to set up a Blazor client-side app with authentication through AWS Cognito.
When I run the app I'm not redirected to a login page, instead the page says "Authorizing..." for a few seconds, while I get this error in the console:
The loading of “https://blazorapp.auth.eu-central-1.amazoncognito.com/login?…Q&code_challenge_method=S256&prompt=none&response_mode=query” in a frame is denied by “X-Frame-Options“ directive set to “DENY“.
This error page has no error code in its security info
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
Then, the default "Hello, world!" index page is shown (even though as I understand it, it should not be visible to an unauthenticated user based on App.razor definition?). If I click on "Log in", I get the same error in console, but then after a few seconds the Cognito-hosted login page opens, I am able to log in, I am redirected back to my app, and the app shows the authenticated user's info in top right corner, but the console is a little weird again:
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
Authorization was successful.
Question 1
How can I get rid of these errors and have my app redirect to Cognito login page without ~10s delay?
Question 2
Why is all content in my app visible at all times regardless of whether I'm authenticated or not? It's as if the NotAuthorized node under AuthorizeRouteView in App.razor had no effect at all, unless I am confusing something here
Code:
Program.cs
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = "https://cognito-idp.{aws-region}.amazonaws.com/{cognito-userpoolid}";
options.ProviderOptions.ClientId = "{cognito-clientid}";
options.ProviderOptions.ResponseType = "code";
options.ProviderOptions.RedirectUri = "https://localhost:44306/authentication/login-callback";
options.ProviderOptions.PostLogoutRedirectUri = "https://localhost:44306/authentication/logout-callback";
});
App.razor (as created from template, no modifications)
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<RedirectToLogin />
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
I have only modified the call to AddOidcAuthentication in Program.cs myself, all other files were populated by Visual Studio when creating a Blazor WebAssembly App with Individual User Accounts.
I am struggling to get this to work and would greatly appreciate any help on this topic
EDIT:
Following #aguafrommars's answer I have published the website to Amazon S3 using static website hosting with Amazon CloudFront as CDN, however, the behavior of the published app is exactly the same as described local behavior
To expand on the questions:
Question 1 expanded:
When the page says "Authorizing..." I only get the described error in the console, the Cognito hosted UI is not rendered, only when I click on "Log in" I am either redirected (with major delay) to Cognito hosted UI, or authenticated without redirection (if I signed in before), perhaps this GIF will clear things up:
I might be wrong, but isn't the problem that the Cognito hosted UI is rejecting to be rendered in an iframe? Can my app redirect to the hosted UI in the first place, like it eventually does? Right now I have to wait while X-Frame-Options error is thrown, click on "Log in", wait while another X-Frame-Options error is thrown, and then finally I'm redirected and the flow succeeds (in the gif the UI doesn't show up because I authenticated before in the session)
Question 2 expanded:
The behavior I want to achieve is that if the user is not authenticated, they cannot see any part of the application, instead they are redirected to Cognito hosted UI and only after they are authenticated they can see anything. I tried to play around with Authorize attribute in MainLayout.razor, but the result is always a blank screen, I would like to provide some code and details but I believe the behavior is impacted by errors described in Question 1, which is why I would like to sort it out first
I'm answering this issue which was marked as duplicate here...
The reason for the delay is the timeout waiting for the silent login process (which has a 10s timeout I believe) as mentioned here and here
Root cause is that AWS Cognito is not compliant with the OIDC standards. Which results in the "'X-Frame-Options' to 'DENY'" error in the browser console.
Until the Blazor team allow us to turn off the silent signin from code, the solution was to disable silent login as follows:
Download the Blazor Interop files located in the asp.net repo here to a local folder.
Open the local folder using vs code and install typescript, webpack, yarn, etc if not already installed
npm install -g yarn
npm install -g typescript
npm install -g webpack
Then edit the AuthenticationService.ts file as follows (commenting out the silent signin functionality). Sorry for the long code print.
import { UserManager, UserManagerSettings, User } from 'oidc-client'
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
type ExtendedUserManagerSettings = Writeable<UserManagerSettings & AuthorizeServiceSettings>
type OidcAuthorizeServiceSettings = ExtendedUserManagerSettings | ApiAuthorizationSettings;
function isApiAuthorizationSettings(settings: OidcAuthorizeServiceSettings): settings is ApiAuthorizationSettings {
return settings.hasOwnProperty('configurationEndpoint');
}
interface AuthorizeServiceSettings {
defaultScopes: string[];
}
interface ApiAuthorizationSettings {
configurationEndpoint: string;
}
export interface AccessTokenRequestOptions {
scopes: string[];
returnUrl: string;
}
export interface AccessTokenResult {
status: AccessTokenResultStatus;
token?: AccessToken;
}
export interface AccessToken {
value: string;
expires: Date;
grantedScopes: string[];
}
export enum AccessTokenResultStatus {
Success = 'success',
RequiresRedirect = 'requiresRedirect'
}
export enum AuthenticationResultStatus {
Redirect = 'redirect',
Success = 'success',
Failure = 'failure',
OperationCompleted = 'operationCompleted'
};
export interface AuthenticationResult {
status: AuthenticationResultStatus;
state?: unknown;
message?: string;
}
export interface AuthorizeService {
getUser(): Promise<unknown>;
getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult>;
signIn(state: unknown): Promise<AuthenticationResult>;
completeSignIn(state: unknown): Promise<AuthenticationResult>;
signOut(state: unknown): Promise<AuthenticationResult>;
completeSignOut(url: string): Promise<AuthenticationResult>;
}
class OidcAuthorizeService implements AuthorizeService {
private _userManager: UserManager;
private _intialSilentSignIn: Promise<void> | undefined;
constructor(userManager: UserManager) {
this._userManager = userManager;
}
async trySilentSignIn() {
if (!this._intialSilentSignIn) {
this._intialSilentSignIn = (async () => {
try {
await this._userManager.signinSilent();
} catch (e) {
// It is ok to swallow the exception here.
// The user might not be logged in and in that case it
// is expected for signinSilent to fail and throw
}
})();
}
return this._intialSilentSignIn;
}
async getUser() {
// if (window.parent === window && !window.opener && !window.frameElement && this._userManager.settings.redirect_uri &&
// !location.href.startsWith(this._userManager.settings.redirect_uri)) {
// // If we are not inside a hidden iframe, try authenticating silently.
// await AuthenticationService.instance.trySilentSignIn();
// }
const user = await this._userManager.getUser();
return user && user.profile;
}
async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
const user = await this._userManager.getUser();
if (hasValidAccessToken(user) && hasAllScopes(request, user.scopes)) {
return {
status: AccessTokenResultStatus.Success,
token: {
grantedScopes: user.scopes,
expires: getExpiration(user.expires_in),
value: user.access_token
}
};
} else {
try {
const parameters = request && request.scopes ?
{ scope: request.scopes.join(' ') } : undefined;
const newUser = await this._userManager.signinSilent(parameters);
return {
status: AccessTokenResultStatus.Success,
token: {
grantedScopes: newUser.scopes,
expires: getExpiration(newUser.expires_in),
value: newUser.access_token
}
};
} catch (e) {
return {
status: AccessTokenResultStatus.RequiresRedirect
};
}
}
function hasValidAccessToken(user: User | null): user is User {
return !!(user && user.access_token && !user.expired && user.scopes);
}
function getExpiration(expiresIn: number) {
const now = new Date();
now.setTime(now.getTime() + expiresIn * 1000);
return now;
}
function hasAllScopes(request: AccessTokenRequestOptions | undefined, currentScopes: string[]) {
const set = new Set(currentScopes);
if (request && request.scopes) {
for (const current of request.scopes) {
if (!set.has(current)) {
return false;
}
}
}
return true;
}
}
async signIn(state: unknown) {
try {
await this._userManager.clearStaleState();
await this._userManager.signinRedirect(this.createArguments(state));
return this.redirect();
} catch (redirectError) {
return this.error(this.getExceptionMessage(redirectError));
}
// try {
// await this._userManager.clearStaleState();
// await this._userManager.signinSilent(this.createArguments());
// return this.success(state);
// } catch (silentError) {
// try {
// await this._userManager.clearStaleState();
// await this._userManager.signinRedirect(this.createArguments(state));
// return this.redirect();
// } catch (redirectError) {
// return this.error(this.getExceptionMessage(redirectError));
// }
// }
}
async completeSignIn(url: string) {
const requiresLogin = await this.loginRequired(url);
const stateExists = await this.stateExists(url);
try {
const user = await this._userManager.signinCallback(url);
if (window.self !== window.top) {
return this.operationCompleted();
} else {
return this.success(user && user.state);
}
} catch (error) {
if (requiresLogin || window.self !== window.top || !stateExists) {
return this.operationCompleted();
}
return this.error('There was an error signing in.');
}
}
async signOut(state: unknown) {
try {
if (!(await this._userManager.metadataService.getEndSessionEndpoint())) {
await this._userManager.removeUser();
return this.success(state);
}
await this._userManager.signoutRedirect(this.createArguments(state));
return this.redirect();
} catch (redirectSignOutError) {
return this.error(this.getExceptionMessage(redirectSignOutError));
}
}
async completeSignOut(url: string) {
try {
if (await this.stateExists(url)) {
const response = await this._userManager.signoutCallback(url);
return this.success(response && response.state);
} else {
return this.operationCompleted();
}
} catch (error) {
return this.error(this.getExceptionMessage(error));
}
}
private getExceptionMessage(error: any) {
if (isOidcError(error)) {
return error.error_description;
} else if (isRegularError(error)) {
return error.message;
} else {
return error.toString();
}
function isOidcError(error: any): error is (Oidc.SigninResponse & Oidc.SignoutResponse) {
return error && error.error_description;
}
function isRegularError(error: any): error is Error {
return error && error.message;
}
}
private async stateExists(url: string) {
const stateParam = new URLSearchParams(new URL(url).search).get('state');
if (stateParam && this._userManager.settings.stateStore) {
return await this._userManager.settings.stateStore.get(stateParam);
} else {
return undefined;
}
}
private async loginRequired(url: string) {
const errorParameter = new URLSearchParams(new URL(url).search).get('error');
if (errorParameter && this._userManager.settings.stateStore) {
const error = await this._userManager.settings.stateStore.get(errorParameter);
return error === 'login_required';
} else {
return false;
}
}
private createArguments(state?: unknown) {
return { useReplaceToNavigate: true, data: state };
}
private error(message: string) {
return { status: AuthenticationResultStatus.Failure, errorMessage: message };
}
private success(state: unknown) {
return { status: AuthenticationResultStatus.Success, state };
}
private redirect() {
return { status: AuthenticationResultStatus.Redirect };
}
private operationCompleted() {
return { status: AuthenticationResultStatus.OperationCompleted };
}
}
export class AuthenticationService {
static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication';
static _initialized: Promise<void>;
static instance: OidcAuthorizeService;
static _pendingOperations: { [key: string]: Promise<AuthenticationResult> | undefined } = {}
public static init(settings: UserManagerSettings & AuthorizeServiceSettings) {
// Multiple initializations can start concurrently and we want to avoid that.
// In order to do so, we create an initialization promise and the first call to init
// tries to initialize the app and sets up a promise other calls can await on.
if (!AuthenticationService._initialized) {
AuthenticationService._initialized = AuthenticationService.initializeCore(settings);
}
return AuthenticationService._initialized;
}
public static handleCallback() {
return AuthenticationService.initializeCore();
}
private static async initializeCore(settings?: UserManagerSettings & AuthorizeServiceSettings) {
const finalSettings = settings || AuthenticationService.resolveCachedSettings();
if (!settings && finalSettings) {
const userManager = AuthenticationService.createUserManagerCore(finalSettings);
if (window.parent !== window && !window.opener && (window.frameElement && userManager.settings.redirect_uri &&
location.href.startsWith(userManager.settings.redirect_uri))) {
// If we are inside a hidden iframe, try completing the sign in early.
// This prevents loading the blazor app inside a hidden iframe, which speeds up the authentication operations
// and avoids wasting resources (CPU and memory from bootstrapping the Blazor app)
AuthenticationService.instance = new OidcAuthorizeService(userManager);
// This makes sure that if the blazor app has time to load inside the hidden iframe,
// it is not able to perform another auth operation until this operation has completed.
AuthenticationService._initialized = (async (): Promise<void> => {
await AuthenticationService.instance.completeSignIn(location.href);
return;
})();
}
} else if (settings) {
const userManager = await AuthenticationService.createUserManager(settings);
AuthenticationService.instance = new OidcAuthorizeService(userManager);
} else {
// HandleCallback gets called unconditionally, so we do nothing for normal paths.
// Cached settings are only used on handling the redirect_uri path and if the settings are not there
// the app will fallback to the default logic for handling the redirect.
}
}
private static resolveCachedSettings(): UserManagerSettings | undefined {
const cachedSettings = window.sessionStorage.getItem(`${AuthenticationService._infrastructureKey}.CachedAuthSettings`);
return cachedSettings ? JSON.parse(cachedSettings) : undefined;
}
public static getUser() {
return AuthenticationService.instance.getUser();
}
public static getAccessToken(options: AccessTokenRequestOptions) {
return AuthenticationService.instance.getAccessToken(options);
}
public static signIn(state: unknown) {
return AuthenticationService.instance.signIn(state);
}
public static async completeSignIn(url: string) {
let operation = this._pendingOperations[url];
if (!operation) {
operation = AuthenticationService.instance.completeSignIn(url);
await operation;
delete this._pendingOperations[url];
}
return operation;
}
public static signOut(state: unknown) {
return AuthenticationService.instance.signOut(state);
}
public static async completeSignOut(url: string) {
let operation = this._pendingOperations[url];
if (!operation) {
operation = AuthenticationService.instance.completeSignOut(url);
await operation;
delete this._pendingOperations[url];
}
return operation;
}
private static async createUserManager(settings: OidcAuthorizeServiceSettings): Promise<UserManager> {
let finalSettings: UserManagerSettings;
if (isApiAuthorizationSettings(settings)) {
const response = await fetch(settings.configurationEndpoint);
if (!response.ok) {
throw new Error(`Could not load settings from '${settings.configurationEndpoint}'`);
}
const downloadedSettings = await response.json();
finalSettings = downloadedSettings;
} else {
if (!settings.scope) {
settings.scope = settings.defaultScopes.join(' ');
}
if (settings.response_type === null) {
// If the response type is not set, it gets serialized as null. OIDC-client behaves differently than when the value is undefined, so we explicitly check for a null value and remove the property instead.
delete settings.response_type;
}
finalSettings = settings;
}
window.sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.CachedAuthSettings`, JSON.stringify(finalSettings));
return AuthenticationService.createUserManagerCore(finalSettings);
}
private static createUserManagerCore(finalSettings: UserManagerSettings) {
const userManager = new UserManager(finalSettings);
userManager.events.addUserSignedOut(async () => {
userManager.removeUser();
});
return userManager;
}
}
declare global {
interface Window { AuthenticationService: AuthenticationService }
}
AuthenticationService.handleCallback();
window.AuthenticationService = AuthenticationService;
Then build the js with
yarn build:release
Once the js file is compiled, copy the AuthenticationService.js file into the/wwwroot directory of your Blazor WASM app.
Then in the index.html file, comment out the MS script and replace with your own:
<!-- <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>-->
<script src="AuthenticationService.js"></script>
Run your app and Cognito will now be (relatively) instantaneous
I ended up switching from Cognito to Auth0 and upgrading from Api Gateway's RestApi to HttpApi, which includes built in JWT authorizer, and I am very happy with the change. Cognito just had too many problems in the end, but if someone is determined to get it to work check #aguafrommars's comments under the accepted answer.
Response 1:
While the authorizing message is displayed, the app check for a valid authentication and set up auto renew token iframe. If you look at the network log on your browser you'll see requests made by this time.
When the app run in release it's faster.
Response 2:
You need to add authorization on pages you want to protect by adding the Authorize attribute.
#page "/"
#attribute [Authorize]
Had the same problem and switched out to Azure B2C which again resolved the issue. Appears to be a problem with the auth library when linking to AWS Cognito as the auth provider.
Issues raised with MS -
https://github.com/dotnet/aspnetcore/issues/22651

I can't get pushToken in Safari

In my work, we tried integrate Safari Push unsuccessful. We have the files .p12 and the .cer
When executing in Safari, not work, no response. If change the name of callback, for example for console.log('hi')... print "hi" and in the console, I see the error
"TypeError: undefined in not an object (evaluating
'window.safari.pushNotification.requestPErmission('https://fabse.tv,
pushId, { user: '123456'}, console.log('hi'))')"
This is my code:
my pushId: 'web.fbase.tv'
setupPushWeb() {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var checkRemotePermission = function (permissionData) {
if (permissionData.permission === 'default') {
// This is a new web service URL and its validity is unknown.
window.safari.pushNotification.requestPermission(
'https://fbase.tv', // The web service URL.
'web.fbase.tv', // The Website Push ID.
{user_id: '4741619481'}, // Data used to help you identify the user.
console.log('hola') // The callback function.
);
}
else if (permissionData.permission === 'denied') {
// The user said no.
}
else if (permissionData.permission === 'granted') {
// The web service URL is a valid push provider, and the user said yes.
// permissionData.deviceToken is now available to use.
}
}
if(isSafari) {
if ('safari' in window && 'pushNotification' in window.safari) {
var permissionData = window.safari.pushNotification.permission('web.fbase.tv');
checkRemotePermission(permissionData);
}
} else {
//Firebase integration Fine

Solidity issue passing parameters to a function

I have a Smart Contract with the function:
contract Example {
event claimed(address owner);
function claimStar() public {
owner = msg.sender;
emit claimed(msg.sender);
}
}
I'm using Truffle V5.0 and the Webpack box as a boiler plate code.
In my truffle-config.js file I have the in the networks configuration:
development:{
host:"127.0.0.1",
port: 9545,
network_id:"*"
}
Everything compile fine using:
- truffle develop
- compile
- migrate --reset
It says Truffle Develop started at http://127.0.0.1:9545
In my index.js file I have the following code:
import Web3 from "web3";
import starNotaryArtifact from "../../build/contracts/StarNotary.json";
const App = {
web3: null,
account: null,
meta: null,
start: async function() {
const { web3 } = this;
try {
// get contract instance
const networkId = await web3.eth.net.getId();
const deployedNetwork = starNotaryArtifact.networks[networkId];
this.meta = new web3.eth.Contract(
starNotaryArtifact.abi,
deployedNetwork.address,
);
// get accounts
const accounts = await web3.eth.getAccounts();
this.account = accounts[0];
} catch (error) {
console.error("Could not connect to contract or chain.");
}
},
setStatus: function(message) {
const status = document.getElementById("status");
status.innerHTML = message;
},
claimStarFunc: async function(){
const { claimStar } = this.meta.methods;
await claimStar();
App.setStatus("New Star Owner is " + this.account + ".");
}
};
window.App = App;
window.addEventListener("load", async function() {
if (window.ethereum) {
// use MetaMask's provider
App.web3 = new Web3(window.ethereum);
await window.ethereum.enable(); // get permission to access accounts
} else {
console.warn("No web3 detected. Falling back to http://127.0.0.1:9545. You should remove this fallback when you deploy live",);
// fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
App.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:9545"),);
}
App.start();
});
In my browser I have Metamask installed and I added a Private Network with the same URL and also imported two accounts.
When I start the application and opened in the browser it opens Metamask to request permission because I'm using window.ethereum.enable();.
But when I click on the button to claim it doesn't do anything.
The normal behavior is to Metamask open a prompt to ask for confirmation but it never happen.
I created also a new property in the contract for test and it works fine showing me the value assigned in the contract constructor.
My question is, Am I missing something?
I also tried to change the functionawait claimStar(); to await claimStar({from: this.account}); but in this case I got an error saying that claimStar doesn't expect parameters.
I would appreciate any help. Thanks
I solved the issue, the problem was in the function claimStarFunc
It should be in this way:
claimStarFunc: async function(){
const { claimStar } = this.meta.methods;
await claimStar().send({from:this.account});
App.setStatus("New Star Owner is " + this.account + ".");
}
Because I'm sending a transaction.
Thanks

Can I set authorization headers with RequireJS?

We want to have 2 sets of resources for our AngularJS app (public/private) which uses RequireJS for dependency management. Basically everything on the login page would be public and once logged in, another angularjs app would be loaded (new requirejs config) that would load resources that require authentication to access.
Is there a way to configure requirejs to set an authorization header when loading resources?
It depends on what you mean by "resources" and how your server is configured. But in general - yes, since you are using AngularJS you can use the $httpProvider to inject an interceptor service.
For example, in a service:
var dependencies = ['$rootScope', 'userService'];
var service = function ($rootScope, userService) {
return {
request: function(config) {
var currentUser = userService.getCurrentUser();
var access_token = currentUser ? currentUser.access_token : null;
if(access_token) {
config.headers.authorization = access_token;
}
return config;
},
responseError: function (response) {
if(response.status === 401) {
$rootScope.$broadcast('unauthorized');
}
return response;
}
};
};
module.factory(name, dependencies.concat(service));
Then, after you configure your routes, you can use:
$httpProvider.interceptors.push( 'someService');
You can find some more information on interceptors here: https://docs.angularjs.org/api/ng/service/$http#interceptors
UPDATE
You might be able to use the text plugin to try and receive it, but I don't see the point in protecting client side code. Plus, if you want to use optimization the resources will just come in one file anyway...
config: {
text: {
onXhr: function (xhr, url) {
xhr.setRequestHeader('Authorization','Basic ' + token);
}
}
}
Refer to: custom-xhr-hooks
Another UPDATE
You could also use urlArgs (mainly used for cache invalidation) without using the text plugin:
require.config({
urlArgs: 'token='+token,
...
)}

Ember.js Ember Simple Auth persist authentication information in LocalStorage does not work

I use Ember Simple Auth with the following settings:
Note: I use Ember App Kit.
app.js
// init Ember.SimpleAuth
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.setup(application, { // #todo at version 0.1.2 of Ember-simple-auth, add container variable
crossOriginWhitelist: ['http://customdomain'],
// store: Ember.SimpleAuth.Stores.LocalStorage, // default now
authenticationRoute: 'article.login'
});
}
});
export
default App;
a simple loginController
(took it mostly from Ember App Kit Simple Auth)
var CustomAuthenticator = Ember.SimpleAuth.Authenticators.OAuth2.extend({
serverTokenEndpoint: 'http://customdomain/access_token/',
makeRequest: function(data) {
return Ember.$.ajax({
url: this.serverTokenEndpoint,
type: 'POST',
data: {
grant_type: 'password',
username: data.username,
password: data.password
},
dataType: 'json',
contentType: 'application/x-www-form-urlencoded'
});
}
});
var LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, {
authenticator: CustomAuthenticator,
actions: {
// display an error when logging in fails
sessionAuthenticationFailed: function(message) {
console.log('sessionAuthenticationFailed');
this.set('errorMessage', message);
},
// handle login success
sessionAuthenticationSucceeded: function() {
console.log('sessionAuthenticationSucceeded');
this.set('errorMessage', "");
this.set('identification', "");
this.set('password', "");
this._super();
}
}
});
export
default LoginController;
So far so good, I can authenticate a user thought a login form. However when I press F5, I have to login again. The LocalStorage adapter is empty. So the question is what do I need to persist the token and session?
Note: I cannot update to ember-simple-auth 0.1.2, bower cannot find the new version. Seems that the github version of https://github.com/simplabs/ember-simple-auth-component is not up to date.
Edit:
I have updated my code as follows:
app.js
// init Ember.SimpleAuth
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.Authenticators.OAuth2.reopen({
serverTokenEndpoint: 'http://customdomain/access_token'
});
Ember.SimpleAuth.setup(container, application, { // #todo at version 0.1.2 of Ember-simple-auth, add container
crossOriginWhitelist: ['http://customdomain'], // #todo remove when live
// store: Ember.SimpleAuth.Stores.LocalStorage,
authenticationRoute: 'article.login'
});
}
});
export default App;
loginController:
var LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, {
// authenticator: CustomAuthenticator, // not needed anymore
actions: {
// display an error when logging in fails
sessionAuthenticationFailed: function(message) {
this.set('errorMessage', message);
},
// handle login success
sessionAuthenticationSucceeded: function() {
this.set('errorMessage', "");
this.set('identification', "");
this.set('password', "");
this._super();
}
}
});
export default LoginController;
I haven't used the oauth2 authenticator before (just a custom one for my backend that I wrote) but I think the same concepts should apply.
When you refresh the page ember-simple-auth makes a call to the restore method of the oauth2 authenticator that you are using. The restore method is looking for a property called 'access_token' to confirm that the user has already authenticated with your server. Does your REST API return a property called access_token when you authenticate with the endpoint at http://customdomain/access_token/? If not, you want to make sure this is happening or you will encounter the refresh issue you're having. Here's the restore method in the oauth2 authenticator provided with ember-simple auth:
restore: function(properties) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
// It looks for the 'access_token' property here which should have been set
// by the authenticate method if you returned it from your REST API
if (!Ember.isEmpty(properties.access_token)) {
_this.scheduleAccessTokenRefresh(properties.expires_in,
properties.expires_at,
properties.refresh_token);
resolve(properties);
} else {
reject();
}
});
}
Additionally, I think in your sessionAuthenticationSucceeded action you need to return true. Otherwise the action won't propagate up to the ember-simple-auth ApplicationRouteMixin (unless you are not using that mixin or don't depend on its sessionAuthenticationSucceeded method in which case it doesn't matter).
This should be fixed with 0.1.2: github.com/simplabs/ember-simple-auth/releases/tag/0.1.2
I also just updated github.com/simplabs/ember-simple-auth-component