I am getting a POST https://heroku_client_path.herokuapp.com/login 405 (Not Allowed) for an Ember.js app with a Rails api (both on heroku)
When processing a login or sign up. I feel the Request URL should be the heroku server path /login as I set my ADAPTER_URL heroku config var, and not the heroku client URL shown above.
I believe I have CORS setup correctly.
I am using Ember-CLI. I hand rolled the auth it is not Simple-Auth.
environment.js:
module.exports = function(environment) {
var ENV = {
modulePrefix: 'client-rantly',
environment: environment,
baseURL: '/',
adapterURL: process.env.ADAPTER_URL,
locationType: 'auto',
EmberENV: {
FEATURES: {
}
},
};
if (environment === 'development') {
}
if (environment === 'production') {
}
return ENV;
};
adapters/application.js:
import DS from 'ember-data';
import ENV from '../config/environment';
export default DS.ActiveModelAdapter.extend({
host: ENV.adapterURL || ENV.ADAPTER_URL,
headers: function () {
return {
'auth_token': localStorage.getItem('authToken')
};
}.property('authToken')
});
brocfile.js
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var app = new EmberApp({
dotEnv: {
clientAllowedKeys: ['ADAPTER_URL']
}
});
module.exports = app.toTree();
controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
needs: ['search'],
isAuthenticated: false,
init: function() {
var authToken = localStorage.getItem('authToken');
if(authToken) {
this.isAuthenticated = true;
}
},
actions: {
login: function () {
var credentials = {
email: this.get('email'),
password: this.get('password')
};
this.set('errorMessage', null);
return Ember.$.post('login', credentials).then(function(response){
this.set('errorMessage', response.error);
if (response.auth_token) {
localStorage.setItem('authToken', response.auth_token);
localStorage.setItem('userId', response.user_id);
this.set('isAuthenticated', true);
location.reload();
}
}.bind(this));
},
}
});
cors rails side - config/application.rb
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module ServerRantly
class Application < Rails::Application
config.active_record.raise_in_transactional_callbacks = true
config.autoload_paths << Rails.root.join('lib')
config.middleware.insert_before 0, "Rack::Cors", :debug => true, :logger => (-> { Rails.logger }) do
allow do
origins '*'
resource '/cors',
:headers => :any,
:methods => [:post],
:credentials => true,
:max_age => 0
resource '*',
:headers => :any,
:methods => [:get, :post, :delete, :put, :options, :head],
:max_age => 0
end
end
end
end
On this line:
adapterURL: process.env.ADAPTER_URL
Where is process.env.ADAPTER_URL defined?
It seems like you are on the right track with overriding host on the ActiveModelAdapter adapter to use your non-client URL.
Related
I have a curious CORS error in a Rails API setup with post request from Vue signup,
Despite setting the values at localhost:8080 for the Vue app and localhost:3000 for the response, it still wont allow access.
Rails CORs settings are below.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:8080'
resource 'localhost:3000',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
The request method using Nuxt axios plugin is below'
// Check submit
signin () {
this.$plain.post( '/api/v1/sign_up/', { email: this.email, password: this.password, password_confirmation: this.password_confirmation} )
.then(response => this.signinSuccessful(response))
.catch(error => this.signinFail)
console.log({ email: this.email, password: this.password, password_confirmation: this.password_confirmation });
alert('Processing!');
},
And the axios plugin code itself is also below.
/* eslint-disable */
import axios from 'axios'
export default function ({ $axios, store }, inject) {
const API_URL = "http://localhost:3000"
const secured = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
"Content-Type": "application/json",
},
})
const plain = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
"Content-Type": "application/json"
},
})
secured.interceptors.request.use((config) => {
const method = config.method.toUpperCase();
if (method !== "OPTIONS" && method !== "GET") {
config.headers = {
...config.headers,
"X-CSRF-TOKEN": localStorage.csrf,
};
}
return config;
});
secured.interceptors.request.use(null, (error) => {
if (error.response && error.response.config && error.response.status === 401) {
return plain
.post("/refresh", {}, { headers: { "X-CSRF-TOKEN": localStorage.csrf } })
.then((response) => {
localStorage.csrf = response.data.csrf;
localStorage.signedIn = true;
let retryConfig = error.response.config;
retryConfig.headers["X-CSRF"] = localStorage.csrf;
return plain.request(retryConfig);
})
.catch((error) => {
delete localStorage.csrf;
delete localStorage.signedIn;
location.replace("/");
return Promise.reject(error);
});
} else {
return Promise.reject(error);
}
})
inject('plain', plain)
inject('secured', secured)
}
It is getting rejected due to CORs policy for authenticated requests as in screenshot, and I tried changing "with-credentials" to false but got error bad request, any tips on this welcome?
Versions:
keycloak 12.0.2
nuxt: 2.14.6
nuxt/auth-next: 5.0.0-1622918202.e815752
Configs:
nuxt.config.js
auth: {
strategies: {
keycloak: {
scheme: '~/plugins/keycloak.js',
endpoints: {
authorization:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/auth',
token:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/token',
userInfo: "https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/token",
logout:'https://keycloak.bgzchina.com/auth/realms/bgzchina/protocol/openid-connect/logout',
},
responseType: 'id_token token',
clientId: 'centuari-portal-fe',
scope: ['openid'],
}
},
redirect: {
login: '/login',
logout: '/logout',
callback: '/callback',
home: '/',
}
},
router: {
middleware: ['auth']
},
due to a issue with current version nuxt/auth-next, I created a custom scheme by extending oauth2
/plugin/keycloak.js
import { Oauth2Scheme } from '~auth/runtime'
function encodeQuery(queryObject) {
return Object.entries(queryObject)
.filter(([_key, value]) => typeof value !== 'undefined')
.map(([key, value]) => encodeURIComponent(key) + (value != null ? '=' + encodeURIComponent(value) : ''))
.join('&')
}
export default class KeycloakScheme extends Oauth2Scheme {
logout() {
if (this.options.endpoints.logout) {
const opts = {
client_id: this.options.clientId,
post_logout_redirect_uri: this._logoutRedirectURI
}
const url = this.options.endpoints.logout + '?' + encodeQuery(opts)
window.location.replace(url)
}
return this.$auth.reset()
}
}
but when doing login, browser will block the token request due to CORS. keycloak server response for the preflight specify allowed method is POST, OPTIONS, but auth-next use GET to fetch token.
Is there any work around ?
You need to add/register the url into keycloak admin dashboard.
Go to keycloak admin dashboard
Menu Clients => select the client
On Settings tab, scroll down the page and find Web Origins. Add your frontend url (nuxt url) on it. Don't forget to add into Valid Redirect URIs too.
I'm authenticating to Laravel (7.3) Passport with the following configuration:
nuxt.config.js
auth: {
redirect: {
login: '/login',
logout: '/login',
home: '/'
},
strategies: {
'laravel.passport': {
url: 'http://laravel.test',
client_id: '2',
client_secret: 'S0gpcgfIDgbvIHCL3jIhSICAiTsTUMOR0k5mdaCi',
redirect_uri: 'http://localhost:3000'
}
}
}
Authentication method in pages/login.vue:
async nuxtLaravelPassport() {
try {
const response = await this.$auth
.loginWith('laravel.passport')
.then(result => {
console.log(result)
})
} catch (err) {
console.log(err)
}
},
It brings me to the authentication page of Laravel, then I log in and I'm redirected to my Nuxt.js home page with a code and state as parameters.
What should I do with these code and state ? Get a token ? If yes, how ?
I've got a Web API which I've secured with OAuth2 but I'm having difficulty getting Swagger UI to show the authentication option.
Currently, the api_key section of UI still shows, despite there being no configuration for it.
Here's my SwaggerConfig
public class SwaggerConfig
{
public static void Register()
{
if (ConfigUtil.SSOSupported)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
// Swashbuckle.Application.OAuth2SchemeBuilder
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.Schemes(new[] { "http", "https" });
c.SingleApiVersion("v1", "API Adapter");
c.PrettyPrint();
c.ApiKey(string.Empty);
c.OAuth2("oauth2")
.Description("Description here")
.Flow("implicit")
.AuthorizationUrl(ConfigUtil.SSOAuthority() + "/connect/authorize")
.Scopes(scopes =>
{
scopes.Add("api", "api");
});
c.IgnoreObsoleteProperties();
c.DescribeAllEnumsAsStrings();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support("your-client-id", "your-client-secret-if-required", "your-realms", "your-app-name");
});
}
}
}
This section is included in page source after loading:
window.swashbuckleConfig = {
rootUrl: 'http://localhost:44390',
discoveryPaths: arrayFrom('swagger/docs/v1'),
booleanValues: arrayFrom('true|false'),
validatorUrl: stringOrNullFrom(''),
customScripts: arrayFrom(''),
docExpansion: 'none',
supportedSubmitMethods: arrayFrom('get|put|post|delete|options|head|patch'),
oAuth2Enabled: ('true' == 'true'),
oAuth2ClientId: 'your-client-id',
oAuth2ClientSecret: 'your-client-secret-if-required',
oAuth2Realm: 'your-realms',
oAuth2AppName: 'your-app-name',
oAuth2ScopeSeperator: ' ',
oAuth2AdditionalQueryStringParams: JSON.parse('{}'),
apiKeyName: 'api_key',
apiKeyIn: 'query'
};
And the iniOAuth function is called:
window.swaggerUi = new SwaggerUi({
url: swashbuckleConfig.rootUrl + "/" + swashbuckleConfig.discoveryPaths[0],
dom_id: "swagger-ui-container",
booleanValues: swashbuckleConfig.booleanValues,
supportedSubmitMethods: swashbuckleConfig.supportedSubmitMethods,
onComplete: function(swaggerApi, swaggerUi){
if (typeof initOAuth == "function" && swashbuckleConfig.oAuth2Enabled) {
initOAuth({
clientId: swashbuckleConfig.oAuth2ClientId,
clientSecret: swashbuckleConfig.oAuth2ClientSecret,
realm: swashbuckleConfig.oAuth2Realm,
appName: swashbuckleConfig.oAuth2AppName,
scopeSeparator: swashbuckleConfig.oAuth2ScopeSeperator,
additionalQueryStringParams: swashbuckleConfig.oAuth2AdditionalQueryStringParams
});
But I just get the api_key text box and no Authorize button like I'd expect.
The controllers and actions all display without an issue.
I'm sure it's something small, but I've been fiddling for a couple of days now and have run out of ideas.
I have been trying to implement direct upload to S3 using the FineUploader JS library. I have a post action in Rails that builds and returns a S3 policy and signature, I have tried many different options but I keep getting an error with the policy in FineUploader.
Here is the FineUploader JS I am using:
<script>
$('#fine-uploader-s3').fineUploaderS3({
template: 'qq-template-s3',
request: {
endpoint: "https://mybucket.s3.amazonaws.com/",
accessKey: MY_ACCESS_KEY
},
signature: {
endpoint: "/generatesignature"
},
uploadSuccess: {
endpoint: "/success",
params: {
isBrowserPreviewCapable: qq.supportedFeatures.imagePreviews
}
},
iframeSupport: {
localBlankPagePath: "/server/success.html"
},
cors: {
expected: true
},
chunking: {
enabled: true
},
resume: {
enabled: true
},
deleteFile: {
enabled: true,
method: "POST",
endpoint: "http://s3-demo.fineuploader.com/s3demo-thumbnails-cors.php"
},
validation: {
itemLimit: 5,
sizeLimit: 15000000
},
thumbnails: {
placeholders: {
notAvailablePath: "/plugins/fineuploader/placeholders/not_available-generic.png",
waitingPath: "/plugins/fineuploader/placeholders/waiting-generic.png"
}
},
callbacks: {
onComplete: function(id, name, response) {
var previewLink = qq(this.getItemByFileId(id)).getByClass('preview-link')[0];
if (response.success) {
previewLink.setAttribute("href", response.tempLink)
}
}
}
});
</script>
Here is the generatesignature action serverside in Ruby
def generatesignature
bucket = MY_BUCKET
access_key = MY_ACCESS_KEY
secret = MY_SECRET_KEY
key = "test.txt/"
expiration = 5.minutes.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z')
max_filesize = 2.megabytes
acl = 'public-read'
sas = '200' # Tells amazon to redirect after success instead of returning xml
policy = Base64.encode64(
"{'expiration': '#{expiration}',
'conditions': [
{'bucket': '#{bucket}'},
{'acl': '#{acl}'},
{'Cache-Control': 'max-age=31536000'},
['starts-with', '$key', '#{key}'],
['content-length-range', 1, #{max_filesize}]
]}
").gsub(/\n|\r/, '')
signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret, policy)).gsub(/\n| |\r/, '')
{:access_key => access_key, :key => key, :policy => policy, :signature => signature, :sas => sas, :bucket => bucket, :acl => acl, :expiration => expiration}
params[:signature]= signature
params[:policy] = policy
render :json => params, :status => 200 and return
end
The error that I am receiving when trying to upload to S3 is:
"Invalid according to Policy: Policy Condition failed: ["eq", "$acl", "public-read"]"
It appears as if you are generating a signature using an ACL value of "public-read", but the policy sent to S3 by Fine Uploader, by default, uses an ACL of "private". If you really want to use a "public-read" ACL, you will need to update the objectProperties.acl Fine Uploader S3 configuration value. For example:
$('#fine-uploader-s3').fineUploaderS3({
objectProperties: {
acl: 'public-read'
},
...
})