I am running a Go server that generates JWT tokens. My original plan was to send the tokens using an http.Redirect using the token string as part of the url.
This doesn't appear to work because I'm using Firebase static hosting and hence only have client side routing.
How can I push my token? Headers maybe?
I'm running my static SPA on 'example.firebaseapp.com' (A).
I'm running my server that generates tokens on 'example.us-west-2.compute.amazonaws.com' (B)
The cas server is running on 'https://login.example.edu/cas/' (C)
There is also of course the user's computer (D)
The flow goes as follows
User load website from static host (A)
User on computer D clicks 'login through school' button and is directed to my server (B)
B then redirects to cas server (C). User puts in his credentials and is redirected to computer B.
Computer B then generates a token using a secret key and a uid.
This token needs to somehow be set back to the user
User would then call ref.authWithCustomToken("AUTH_TOKEN", function(error, authData) {
Go Server Code
func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !cas.IsAuthenticated(r) {
cas.RedirectToLogin(w, r)
return
}
if r.URL.Path == "/logout" {
cas.RedirectToLogout(w, r)
return
}
generatedToken := generateToken("uid") // token is created using a uid and a secret
redirectURL := websiteURL + generatedToken
println(redirectURL)
println(generatedToken)
http.Redirect(w, r, redirectURL, http.StatusFound) // I attempt to send the token using a redirect. This doesn't seem to work though since the static server only supports routing for '/'.
//html.WriteTo(w)
}
If I understand the flow correctly, then what you're missing is an end point that your app user talks to and that can return the token to that user.
A workaround for this would be to have the user app pass in a highly unguessable value (a "request ID") in step 2, something like a UUID. The token server can then write the token into the Firebase Database in step 5 in /tokens/<requestID>, where the client is listening for it.
Related
I'm using the Gin framework. I have a database that contains some course info. Users can register in the courses and access the contents. The contents are image, video, and audio.
I store the relative location of these contents in my database like this:
Content\Courses\CourseOne\Unit_1\image\1.jpg
and change it to the actual location in gin:
route := gin.Default()
route.Static("/Content","./Media")
Everything works fine, but I am looking for a way to authenticate users before accessing the contents. In the above-mentioned way, all users can access any data by changing the desired pattern's address. But I want if the user is registered in the course, be able to access data, otherwise, get a 404 error.
how can I do that?
Edit
since it was asked to explain the implementation of authentication:
I used JWT for authentication. so each user has a HashID.
I have a table called UserCourses and the user info would be inserted after purchasing a course.
this is my course route:
route.GET("api/v1/courses", handler.GetCourses)
and my handler:
func GetCourses(context *gin.Context) {
hashID, status, err := repository.GetToken(context)
if err != nil {
context.IndentedJSON(status, err)
return
}
courses := make([]model.CourseAPI, 0)
userInfo := model.Users{HashID: hashID}
err = repository.DatabaseInstance.GetCourses(&courses, &userInfo)
if err != nil {
context.IndentedJSON(http.StatusServiceUnavailable, err)
return
}
context.IndentedJSON(http.StatusOK, gin.H{"courses": courses})
}
The JWT token is passed by the client in the header. so I get the token and validate it. The token contains the user HashID and I check for that HashID in the UserCourses table. besides the course info, there is a variable called isRegistered.if the HashID was registered for any course in UserCourses table,the isRegistered become true for that course otherwise false.
You can create group route and apply authentication middleware through it
r = gin.Default()
// public routes
r.GET("public", publicHandler)
// routes group
auth = r.Group("/")
// authentication middleware within group
auth.Use(AuthMiddleware())
// route before which auth middleware will be run
auth.Static("/Content","./Media")
I am using a custom logging handler in my Go web server like this:
func main() {
// ... Set up everything
router = mux.NewRouter()
router.Handle("/apilookup",
raven.Recoverer(
jwtMiddleware.Handler(
http.HandlerFunc(
doApiLookup)))).Methods("GET")
loggedRouter := handlers.CustomLoggingHandler(os.Stdout, router, writeLog)
http.ListenAndServe(listenAddr, loggedRouter)
}
In the writeLog function, I have made my own version of the Gorilla handlers.LoggingHandler, which logs a lot of additional information.
One thing I would like to do is log the user for authenticated requests. Users authenticate to this server using JWT (using the Authorization: Bearer ... header). I am using Auth0's go-jwt-middleware to parse the token and set its value in the Request's context.
I tried to log the user's email address (one of the claims in the JWT) like this, based on the middleware's documentation:
func writeLog(writer io.Writer, params handlers.LogFormatterParams) {
// ... SNIP
// If we can't identify the user
username := "-"
if userJwt := params.Request.Context().Value("user"); userJwt != nil {
claims := userJwt.(*jwt.Token).Claims.(*jwtClaims)
username = claims.Email
}
// ... SNIP
}
The problem is that username is always the initial value - and not the expected value from the JWT.
By adding log.Printf("%+v\n", params.Request.Context()) above the if, I see that the context doesn't actually contain the parsed JWT data here.
As far as I can tell, the reason this is not working is because the middleware creates a new Request with the updated context, so only middleware further down the chain can see it. Because the logging middleware is above the JWT middleware, it does not have that same context.
I know that I can re-parse the JWT in the logging handler (because I do have access to all the headers), but that seems like a lot of overhead for logging.
Is there a better way to do this that will allow me to have access to this data where I want it?
My web app has URLs at three access levels:
Those accessible by anyone (login page and static assets)
Those accessible regular users and admins who are logged in
Those accessible only by admins who are logged in
I should specify the minimum access level for each URL pattern in my router, so that people below that level are blocked. (I suppose they should get HTTP error 401 or 403.)
How do I best implement these checks so that I don't have to remember to put them in every URL handler function separately (which is very easy to forget)? Ideally I'd like to do something like this:
router.Get("/someRegularPage", regularAccess(handleSomeRegularPage))
router.Get("/someAdminPage", adminAccess(handleSomeAdminPage))
router.Get("/", publicAccess(handleLoginPage))
Is there some semi-standard middleware to do this and how does that work? How hard would it be to write my own?
Additionally, it would be great if the default permission was to deny access to everybody in case I forget to specify the access level for some URL. A compiler warning or error would be ideal.
Writing your own is not hard. Assuming you store your admin token in an environment variable called ADMINTOKEN :
func AdminOnly(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization")
if r.Method == "OPTIONS" {
f(w, r)
return
}
h := r.Header.Get("Authorization")
token := strings.TrimPrefix(h, "Bearer ")
if token == os.Getenv("ADMINTOKEN") {
f(w, r)
return
}
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
OPTIONS method may have to be authorized regardless because of CORS.
The short answer is to write middleware to handle authorization. A longer answer that may help you out just as much in the long run would be to use appropriate routing paths for your endpoints alongside that middleware. For instance, you could prefix all of your admin routes with /api/admin and then wrap the router for individual routes after that with the relevant admin-auth middleware.
According to the docs at https://www.godoc.org/golang.org/x/oauth2#Config.AuthCodeURL
...State is a token to protect the user from CSRF attacks. You must always provide a non-zero string...
and at https://www.rfc-editor.org/rfc/rfc6749#section-10.12
...any request sent to the redirection URI endpoint to include a value that binds the request...
Yet this is specifically at the part in the flow when there is no session data, i.e. the user has not logged in and the auth code is only generated upon showing the anonymous page.
How then can this value be randomized and compared upon callback? Is it a static value randomized per server?
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
RFC 6749
You use state to identify that the callback from the authorization server matches the request sent. If there wasn't state a attacker could just call your callback url with a random access token that you didn't request. With state you know that the called callback is in response to the request you made.
So you randomize state per request that you sent and track it until you receive the matching callback. It can be anything you want as long as it can't be guessed.
A simple approach would be leveraging rand.Reader and base64 encoding the result:
func state(n int) (string, error) {
data := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
I am working with a remote API that is normally accessed directly via JavaScript. In the normal flow, The user authenticates by sending Auth headers and in return is granted a cookie.
What I am trying to do is send auth headers from a laravel app, authenticate in the app controller, and provide API access through laravel controller functions.
I was hoping this would be as simple as authenticating and sending my subsequent API calls, hoping that the cookie given to the PHP server would continue to grant authentication.
Well that doesn't work and thats fine, but now I am thinking that I need to store my access cookie in the Session, and send it in the headers for future API calls.
Will this work/how can I go about this? My supervisors don't want to implement OAuth type tokens on the remote server and to me that seems like the best route, so I am a bit stuck.
Cookies cannot be shared across multiple hosts. The cookie (on the client) is only valid for path which set it.
EDIT - ADDING ADDITION AUTH DETAIL
Setting up remember me in Laravel
When migrating (creating) you User table add $table->rememberToken()
to create that column in your User table.
When user signs up to your service add a check box to allow them to
make the decision OR you can just set it true if you don’t to offer
the user the option as described in step 3
< input type="checkbox" name="remember" >
In your controller you add the following code:
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}
Users table must include the string remember_token column per 1. , now assuming you have added the token column to your User table you can pass a boolean value as the second argument to the attempt method, which will keep the user authenticated indefinitely, or until they manually logout. i.e. Auth::attempt([$creditentials], true);
Side note: the Illuminate\Contracts\Auth\UserProvider contract, public function updateRememberToken(Authenticatable $user, $token) uses the user’s UID and token stored in the User table to store the session auth.
AUTH ONCE:
Laravel has once method to log a user into the application for a single request. No sessions or cookies. Used with stateless API.
if (Auth::once($credentials)) {
//
}
OTHER NOTES
The remember cookie doesn't get unset automatically when user logs out. However using the cookie as I explained below in cookies example you could add this to your logout function in your controller just before you return the redirect response after logout.
public function logout() {
// your logout code e.g. notfications, DB updates, etc
// Get remember_me cookie name
$rememberCookie = Auth::getRecallerName();
// Forget the cookie
$forgetCookie = Cookie::forget($rememberCookie);
// return response (in the case of json / JS) or redirect below will work
return Redirect::to('/')->withCookie($forgetCookie);
OR you could q$ueue it up for later if you are elsewhere and cannot return a response immediately
Cookie::queue(forgetCookie);
}
Basic general cookie example that might help you. There are better approaches to do this using a Laravel Service provider
// cookie key
private $myCookieKey = 'myAppCookie';
// example of cookie value but can be any string
private $cookieValue = 'myCompany';
// inside of a controller or a protected abstract class in Controller,
// or setup in a service ... etc.
protected function cookieExample(Request $request)
{
// return true if cookie key
if ($request->has($this->myCookieKey)) {
$valueInsideOfCookie = Cookie::get($this->myCookieKey);
// do something with $valueInsideOfCookie
} else {
// queue a cookie with the next response
Cookie::queue($this->myCookieKey, $this->cookieValue);
}
}
public function exampleControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function one code
}
public function secondControllerFunction(Request $request)
{
$this->cookieExample($request);
// rest of function two code
}