01 logo

Microservices in Practice: Developing Instagram Clone

Part3: Authentication Service

By Amr KhaledPublished 4 years ago 7 min read
3

Token based authentication

In Monolithic applications, a user provides username and password, the security module validates user credentials and create a session for the user. A session Id is returned to the client and the client stores the session Id in cookies and send it with each request as an identifier for the user.

This approach making the service statefull has an impact on the scalability because a session should be stored for each user, the more users you’ll have the more memory you’ll need.

Also if you divide the app into multiple services, you’ll need to find a way to share the session between services.

In the token based authentication, authentication service generates the token and send it to the client but it doesn’t store it. The client has to send the token with every request in the Authorization header. This token holds the user’s identity information.

Here, I’m using JWT token or Json Web Token, from now on when I will refer to JWT token as simply token.

JWT token structure

Since the token holds user information, it is encoded and encrypted.

A JWT token would look like below

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZGFtIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiJdLCJpYXQiOjE1OTE2NDQ2NjMsImV4cCI6MTU5MTczMTA2M30.9pR6sK-kJmG6Zm7aWVqNrQy9MZ8ponYKz3Wc3nfKU7MD4oXkifjAEgkB6Uc-Dswp3yjWIiNLrpMd7S9SZ1_D_A

If you notice there are 3 sections in the token separated by dot “.”, these sections are Header.Payload.Signature

  • Header: contains metadata like type of the token and the hashing algorithm that is used in Signature section.

{

"typ": "JWT",

"alg": "HS512"

}

  • Payload: contains user information and token expiration date.

{

"sub": "adam",

"authorities": [

"USER"

],

"iat": 1591644663,

"exp": 1591731063

}

  • Signature: contains the signature of the token creator, to make sure that the token isn’t changed.

HMACSHA512(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

Secret

)

Auth Service responsibilities

  1. Registering new users.
  2. Validating user credentials and issue a token.
  3. Stores and retrieves users profile.
  4. Manages users.
  5. Sends a message whenever a user created/updated.

Let’s dive deep into code.

Clone the Auth service from GitHub and let’s walk through it.

MongoDB and Apache Kafka (Docker)

Auth service is using MongoDB as the data store for its users, because of scalability, MongoDB has a better performance since it doesn’t have to deal with the overhead of relational databases and it gives a flexibility to evolve the schema as business needs.

Auth service uses Apache Kafka as a way to notify other services whenever user is created, updated or deleted. We can discuss all the “whys” (why Kafka?, why asynchronous? or why notifying other services?) in the comments.

I will be using docker and docker compose for dependencies like MongoDB, Kafka, neo4j, … etc.

If you don’t know what docker and docker compose are, for now, just think of it as a way to have MongoDB and Kafka on your local machine without having to install them on your machine and follow the below instructions.

  1. Install docker and docker compose.
  2. Clone this docker-compose file, comment out Neo4j and Cassandra sections for now.

The file should look like below

Note to change the path “/home/amr/instaMongo” (line 29) to a directory that is on your machine.

3. Navigate to the docker compose file directory and type

docker-compose up

User repository and service

I’m using Spring data MongoDB, all you have to do to provide an interface and the operations (following spring data conventions) and spring will provide the implementations for you.

Nothing fancy here, I think method names are descriptive.

Also, user service is self descriptive, it provides CURD operations for User object (create user, update user, update user profile picture, find user by usernme, … etc). You’ll find it uses UserEventSender to send Kafka messages which I will discuss in the next section.

It is worth to mention InstaUserDetailsService, it is a requirement by Spring Security, you should provide a service that implements UserDetailsService and return UserDetails. I will talk about spring security configurations in a later section.

One last thing to note here, annotating the Application class with @EnableMongoAuditing.

This will make spring to set the below properties in the User model.

@CreatedDate

private Instant createdAt;

@LastModifiedDate

private Instant updatedAt;

Messaging (Kafka messages)

I’m using Spring Could Stream which ease the way to publish and listen to events (messages).

I provide an interface called UserEventStream (It is a better to name it UserEventChannel) as below

Notice the annotation @Output which mean this channel will be used for publishing messages to it. @Input on the other hand means you’ll listen to it.

Now you need to configure you Kafka and map channels to topics.

Let’s look at Kafka configurations in application.yml

The 1nd part configures Kafka brokers (you can think of it as host or read more about Apache Kafka). Since we are deploying locally, the broker is on localhost and 9092 is the default port.

The 2nd part is what maps momentsUserChanged channel to moments.user.changed topic.

Spring cloud stream will automatically create the topic for you if it is not exist.

Finally, UserEventSender uses UserEventStream to send UserCreated and UserUpdated events to moments.user.changed topic.

Don’t forget to annotate the Application class with @EnableBinding as below.

Spring Security Configuration

Let’s look into SecurityCredentialsConfig.

The first thing is to configure the AuthenticationManager to authenticate users from MongoDB.

Line 11, sets the UserDetailsSevice to our userDetailsService that I explained in the previous section. When a user provide a username and password, spring will call loadUserByUsername from userDetailsService and compare passwords if user exists.

In Line 5, it is a hack I did to add a credentials for Service user for service to service communications. Service still need a token to access other services. I add service account in memory as you can see.

So, when you provide username and password, spring will first look into the in memory auth, if not found will look into the DB auth.

The 2nd to look into in SecurityCredentialsConfig is the below method.

Line 4, set the session creation to stateless, so spring won’t save sessions for users.

Line 7, In case of authentication error send an unauthorized http response (401).

Line 9, set the token authentication filter, which will intercept the request and check if the token is in header and validate the token, we will discuss this in details below.

The remaining part secure the access to all service resources except “/signin” and “/users” for POST method, because you want anyone to be able to login and register.

Now, let’s look into JwtTokenAuthenticationFilter.

Line 5–10, gets the authentication header and check if the token is in the header, if not it will pass to the next filter because a user might be accessing unsecured resource like “/signin” or “/users”.

Line 12, tokenProviderService validate the token and if the token is valid it will create a security context for the user depending on if it is a Service user or normal User.

TokenProviderService is responsible for generating and validating user access token.

Auth service endpoints

ooking into UserEndpoint controller which provides an endpoint for authentication and user manipulation.

POST /signin // validate username and password then return a token

POST /users // creates new user

PUT /users/me/picture // updates current user profile pic

GET /users/{username} // retrieve user by username

GET /users // finds all users

GET /users/me //retrieve logged in user

GET /users/summary/{username} // return only username, name, email a

and profile picture for the user.

GET /users/summary/in" // return uses summaries for the specified usernames

Let’s look into “/signin” response because others are just straight forward.

{

"accessToken":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZGFtIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiJdLCJpYXQiOjE1OTE2NDQ2NjMsImV4cCI6MTU5MTczMTA2M30.9pR6sK-kJmG6Zm7aWVqNrQy9MZ8ponYKz3Wc3nfKU7MD4oXkifjAEgkB6Uc-Dswp3yjWIiNLrpMd7S9SZ1_D_A",

"tokenType": "Bearer"

}

It returns a token and the client has to save it somewhere and send it with every request.

Service Discovery Registration

Finally, the service need to register itself in the service discovery, so other services can find it by its name (spring application name), in our case it is “insta-auth”.

If you don’t know what are we talking about, then check part 2 of this series.

To register add the below configurations in application.yml

Now, run Mongo, kafka, insta-discovery and Auth service and play with it.

Open

http://localhost:8761

You should see insta-auth registered, try to create user, login, .. etc.

If you can’t see the service registered in the discovery service or you don’t know how to add the token in the header, please leave a comment to troubleshoot it together.

how to
3

About the Creator

Amr Khaled

I’m a software engineer who is passionate about software architecture and design. Enjoy coding in Java, Scala, and JavaScript.

Reader insights

Be the first to share your insights about this piece.

How does it work?

Add your insights

Comments

There are no comments for this story

Be the first to respond and start the conversation.

Sign in to comment

    Find us on social media

    Miscellaneous links

    • Explore
    • Contact
    • Privacy Policy
    • Terms of Use
    • Support

    © 2024 Creatd, Inc. All Rights Reserved.