my first question at SO, hope I can get any help.
The architecture of the problem is: I have a simple webapp sourced by Sinatra and Slim, and using SSO CAS System for Authentication. Importand detail: Not using Rails.
The CAS filter is done by hand like this:
class App < Sinatra::Application
before do
process_cas_login(request, session)
require_authorization(request, session) unless logged_in?(request, session) end
And the CAS methods do something like this:
def process_cas_login(request, session)
if request[:ticket] && request[:ticket] != session[:ticket]
service_url = read_service_url(request)
st = read_ticket(request[:ticket], service_url)
CAS_CLIENT.validate_service_ticket(st)
if st.success
session[:cas_ticket] = st.ticket
session[:cas_user] = st.user
else
redirect '/'
#raise "Service Ticket validation failed! #{st.failure_code} - #{st.failure_message}"
end
end
end
def logged_in?(request, session)
session[:cas_ticket] && !session[:cas_ticket].empty? end
def require_authorization(request, session)
if !logged_in?(request, session)
service_url = read_service_url(request)
url = CAS_CLIENT.add_service_to_login_url(service_url)
redirect url
end end
The problem is: My backend needs to invoke a REST service from other Sinatra application and mantain the session (ask for a ticket, know the user that invoked that service, stuff like that)
I was reading about Proxying but I couldn't make that happen in my current model, do you have any clues?
There is a walkthrough with Rails filters, but again, I am not using Rails.
Thank you on advance for your help
In your app, instead of calling the service validate url, you need to call the proxy validate url of the CAS server (/proxyValidate) with your current ticket, the service and a pgtUrl parameter.
In the returned XML, you will get a new cas:proxyGrantingTicket tag containing the PGTIOU: it must be stored in a shared map in your app: pgtIou <-> user_identity
This pgtUrl is an url in your application which will receives callbacks from the CAS server with two parameters: pgtIou and pgtId. Using the previous shared map, you know have: pgtId <-> user_identity.
For a user identity, if you want to call a REST service, you need to obtain a ticket for it by calling the /proxy url of the CAS server giving the pgt parameter (the pgtId) and a targetService parameter for the REST service.
Finally, you just need to call your REST service like any CAS service by providing a service ticket. This REST service must protected by the CAS server of course and will in turn perform a service ticket validation via the CAS server.
Related
I have a Spring Boot application and use the Java Apereo CAS Client (version 3.6.2) to use an CAS server for authentication. In other words, I want to turn my app into a CAS client, I didn't set up the CAS server myself.
I checked the list of calls made to CAS server:
The first call to the CAS server is made, but I don't see the second call to the server for ticket validation (i.e., a call to https://cas-server-address/cas/serviceValidate URL) that will return an XML document with user and authtype attributes that I want to extract to store in the database.
I have 2 questions:
Why there is no second call for the CAS server for ticket validation? Is it hidden?
How do I extract user and authtype attributes from the XML document and store them in the database?
Why there is no second call for the CAS server for ticket validation?
There is. The second call is a back-channel call from your application server over to the CAS server. By definition, this is not something you would see in your browser. This call goes over to the CAS server behind the scenes to validate the service ticket received in the first leg (i.e. ST-xyz). The Java CAS client library should be automatically doing this for you, and you can verify this in the logs.
If you don't see this happening, your configuration is not set correctly or there is an error along the process.
Is it hidden?
Hidden from the browser, as it's a back-channel call. For additional details on what happens and why, please study the CAS protocol.
How do I extract user and authtype attributes from the XML document and store them in the database?
The Java CAS client library typically extracts the user id and other attributes. Then, the user-id would be available under the REMOTE-USER header that can be fetched via the http request object. If you have access to the http session, you can also fetch the final Assertion from the session which contains the CAS payload:
var assertion = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
For a more practical example, see this.
In my Spring MVC application I am using spring security. It works fine so far, but I have to admit, I do not understand all the details.
In the same application the user can call some controller functions by rest api. When the user (lets say Tom) does this, his authentication is lost. Instead the controller is called by user anonymous.
I tracked down that "user switch" to the code below. Variable restCall contains an url to my application. That call would work for user Tom, if he would place it in the browser directly. Using the restcall, the user changes to anyonymous.
1) Can I prevent that, when the calling User (Tom) was already logged in?
2) As those services should be called by a user that is not already browsing the web interface of the application, I would have to find a way to login by the rest call itself.
private void callWebservice(HttpServletRequest req, String restCall) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response
= restTemplate.getForEntity(restCall, String.class);
logger.debug(response.toString());
//assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
}
You need, for example, a JSON Web Token (JWT) Authentication.
Steps are the following:
1) Client does an authentication, a post request with username and password
2) If authentication is successful, server returns a token in which is coded the user
3) In your next call, GET or POST, you add JWT to your header, this way the server will know who you are, because server can decode the token, and can grant also the right authorities and functionalities
I'm building an API with Sinatra (using Angular for the client side and want others to have access to API) and have it also be an OAuth provider. I am wondering what the best route to take (work off existing gems or roll own solution off Warden or something).
Have used devise and doorkeeper for authentication and oauth before with Rails, wondering what best solution for Sinatra is.
Ideally I don't want the views or be able to extend/mod the actions of an existing solution, as I'm interacting with it purely as an API.
I just recently did the same thing using the following answer from S/O
What is a very simple authentication scheme for Sinatra/Rack
It implies a user model, but instead of using that, I just set a user and admin password in my config file. Then I had a login form that just took a password. When the user enters that password, I checked it against the one in settings and set the session['user'] to :admin or :user according to whichever it matched (or nil if none). Then on each of my routes, I called auth: :user or auth: :admin accordingly.
APIs normally accept your login request and send you an authentication token which you need to pass back in each call. This is very similar to cookie based sessions where your browser automatically passes back the cookie which is acquired on initial website visit.
From what I've seen in Sinatra's docs, you could make a session-based authentication system like this:
enable :session
disable :show_exceptions
use Rack::Session::Pool,
key: 'session_id'
post '/login' do
user = User.login_success(params)
halt 401 if user.nil?
session[:user] = user
200
end
get '/fun' do
user = session[:user]
halt 401 if user.nil?
halt 403 if !user.has_permission_for '/fun'
"fun was had"
end
Now all you need to do in your client is to pass back the cookie token returned in response to initial visit when requesting an API function. This can be done with any web client library that supports cookie stores (such as libcurl) or by inserting the session cookie into the request header manually. Rack::Minitest functionality also supports cookies, so you can test your API with minitest.
See Sinatra API Authentication.
Quick summary:
Sinatra has no built-in auth.
It's best to build auth yourself (see the link).
There are gems available, but you probably won't need them for something as simple as an API.
The issue here is with multiple instances of the same Sinatra (Rack) app deployed on Passenger+Apache on different sub URIs with HTTP basic auth to keep away unwanted access:
I have 4 instances of a Sinatra app deployed on my domain as:
example.com/private/foo
example.com/private/moo
...
...
Access to all of them is protected by HTTP basic authentication using the Rack::Auth::Basic middleware. config.ru for all of them look like:
# ...
users = {'user' => 'password'}
use Rack::Auth::Basic, 'realm' do |username, password|
users.key?(username) && users[username] == password
end
run MyApp
The only thing the changes from one config.ru to another is the 'realm' parameter.
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs. Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance? One thing I noticed is that the first time I log into one of the instances, Chrome says 'The server at example.com:80 requires a username and password'. I would have expected 'The resource example.com/private/foo requires a username and password'. Isn't that how it is supposed to work?
I checked Rack::Auth::Basic source code and Wikipedia's article on HTTP Basic Auth and came up with nothing to help my case :(.
In basic authentication, the realm parameter isn't send back to the server. So the server can't really check if the client is sending authorization header for the same realm or not. It depends on the client. Rack's implementation of HTTP basic authentication is correct. So:
Now the issue is that once I have logged into one of the apps, say private/foo, Chrome doesn't prompt me for a username and password for other apps (private/moo etc.). This is counterintuitive since all instances are uniquiely identified by their URLs.
As Andrew pointed out and is clear from the RFC, URL doesn't play a role there. But if '/foo' is protected, '/foo/moo' is protected under the same realm.
Using different credentials for each instance does work, but shouldn't Chrome request credentials at least once for each instance?
Under the scenes what is happening (on inspecting with debugger tools) is that, after I have logged once into one of the apps, say private/foo, Chrome re-sends the same authorization header to other apps, say private/moo, without being challenged first.
The RFC says that the client may send the corresponding authorization header for a realm without being challenged by the server first.
Looks like Chrome is either treating all my apps to be under the same realm or re-sending the same authorization header across different realms. I don't think that is the expected behavior but I could be missing something. Firefox behaves same. Anyway, that wasn't the essence of the question.
The theme of the question was "How do I get Chrome to request me username and password at least once for each instance? Basic auth isn't working the way I expected it to; why?"
Use Digest authentication (RFC 2617 again). Rack implements the MD5 algorithm version under Rack::Auth::Digest::MD5. Set different opaque for each instance and you are good to go:
# ...
realm = "Description of the protected area."
opaque = "Secret key that uniquely identifies a realm."
users = {'user' => 'password'}
use Rack::Auth::Digest::MD5, realm, opaque do |username|
users[username]
end
opaque is sent back by the client and can be verified on the server side that the authorization request is for the correct resource. Job of realm seems descriptory in nature -- which area or resource are you trying to protect? what id do I flash?
RFC: https://www.rfc-editor.org/rfc/rfc2617
I managed to make the oauth process work for PIN kind of verification. My twitter application is client type. When enter authorize url into web browser and grant the application access then I have to enter pin in my ruby application. Can I finish the process of getting access token without the pin thing?
What changes do I need to do to make it work without pin?
Could somebody tell me what is the different between client and web application in general? I'd say that it is the same once I get the access token.
My current code is like.
gem 'oauth'
require 'oauth/consumer'
consumer_key = 'your_key'
consumer_secret ='your_secret'
consumer=OAuth::Consumer.new consumer_key,
consumer_secret,
{:site=>"http://twitter.com"}
request_token = consumer.get_request_token
puts request_token.authorize_url
puts "Hit enter when you have completed authorization."
pin = STDIN.readline.chomp
access_token = request_token.get_access_token(:oauth_verifier => pin)
puts
puts access_token.token
puts access_token.secret
You cannot skip the PIN (called verifier) step.
That's when the client authorizes your app to access protected resources.
You can save your users the trouble of copying that number and pasting it in your app if you can host a public web application that exposes a URL where the OAuth Provider can redirect you to, with the verifier or PIN as a querystring parameter.
To do this, you should pass that URL in the oauth_callback parameter instead of sending oob
Edit:
You might want to check the relevant
part of the specification ->
6.2.3